Mutability issue returning a stream from a trait method

Hi all! Ran into an issue I wasn't able to puzzle through when returning an impl Stream from a trait, was hoping someone knows what's up.

Here's a simplified example of the behavior. We have no issue returning a Stream from a struct:

struct WorkingStruct {
    count: usize
}

impl WorkingStruct {
    fn more(&mut self, amount: usize) {
        self.count = self.count + amount;
    }
    
    fn values(&self) -> impl Stream<Item = usize> {
        tokio_stream::iter(0..self.count)
    }
}

#[tokio::main]
async fn main() {
    let mut working = WorkingStruct { count: 10 };
    
    let values = working.values();

    working.more(10);
    
    let values = values.collect::<Vec<usize>>().await;
    println!("Got {} values", values.len());
}

But let's say we want to turn this into a trait:

trait Broken {
    fn more(&mut self, amount: usize);
    fn values(&self) -> impl Stream<Item = usize>;
}

struct BrokenStruct {
    count: usize
}

impl Broken for BrokenStruct {
    fn more(&mut self, amount: usize) {
        self.count = self.count + amount;
    }
    
    fn values(&self) -> impl Stream<Item = usize> {
        tokio_stream::iter(0..self.count)
    }
}

#[tokio::main]
async fn main() {
let mut broken = BrokenStruct { count: 10 };
    
    let values = broken.values();

    broken.more(10); // <-- this breaks
    
    let values = values.collect::<Vec<usize>>().await;
    println!("Got {} values", values.len());
}

This doesn't compile with the following error:

rror[E0502]: cannot borrow `broken` as mutable because it is also borrowed as immutable
  --> src/main.rs:54:5
   |
52 |     let values = broken.values();
   |                  ------ immutable borrow occurs here
53 |
54 |     broken.more(10);
   |     ^^^^^^^^^^^^^^^ mutable borrow occurs here
55 |     
56 |     let values = values.collect::<Vec<usize>>().await;
   |                  ------ immutable borrow later used here

I'm curious what's causing the difference between these two cases, and how I can have get the broken trait case to compile.
Any help is appreciated, thanks!

Playground link

The -> impl Stream<Item = usize> inside the trait implicitly captures the lifetime from &self, whereas the version outside of a trait does not (on edition 2021). You can read more here.

So this doesn't work either, for example.

impl WorkingStruct {
    fn values(&self) -> impl Stream<Item = usize> + use<'_> {

Sadly, opting out of captures with use<> in traits is not yet supported, so that can't be the fix on stable for now.

There is another fix: use an associated type instead of impl Trait.

trait Broken {
    type Stream: Stream<Item = usize>;
    fn more(&mut self, amount: usize);
    fn values(&self) -> Self::Stream;
}

impl Broken for BrokenStruct {
    type Stream = tokio_stream::Iter<std::ops::Range<usize>>;
    fn values(&self) -> Self::Stream {
        tokio_stream::iter(0..self.count)
    }
}

The downside is that you'll have to use Pin<Box<dyn Stream<..>> or the like if the stream is unnameable.

3 Likes