Chowlett
Chowlett

Reputation: 46685

Mutable methods with lifetimes and later references

Consider the following code:

struct Foo<'a> {
    borrowed: &'a u8,
    owned_one: Vec<u8>,
    owned_two: Vec<u8>,
    output: usize
}

impl<'a> Foo<'a> {
    fn do_stuff(&mut self) {
        self.output = self.owned_one.len();
        let zipped = self.owned_one.iter().zip(self.owned_two.iter());
        Self::subroutine(&zipped);
    }
    
    fn subroutine<Arg: Iterator<Item=(&'a u8, &'a u8)>>(_zipped: &Arg) {}
}

fn main() {
    let num = 0u8;
    let mut foo = Foo {
        borrowed: &num,
        owned_one: vec![0],
        owned_two: vec![1],
        output: 0
    };
    foo.do_stuff();
    let _out = &foo.output;
}

(playground link)

It doesn't compile, producing the following error:

error: lifetime may not live long enough
  --> src/lib.rs:12:9
   |
8  | impl<'a> Foo<'a> {
   |      -- lifetime `'a` defined here
9  |     fn do_stuff(&mut self) {
   |                 - let's call the lifetime of this reference `'1`
...
12 |         Self::subroutine(&zipped);
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^ argument requires that `'1` must outlive `'a`

I don't fully understand the complaint - surely self would always have the lifetime assigned to the class we're implementing? - but I can understand that both arguments to zip() need to last the same time. So I change do_stuff to take &'a mut self.

struct Foo<'a> {
    borrowed: &'a u8,
    owned_one: Vec<u8>,
    owned_two: Vec<u8>,
    output: usize
}

impl<'a> Foo<'a> {
    fn do_stuff(&'a mut self) {
        self.output = self.owned_one.len();
        let zipped = self.owned_one.iter().zip(self.owned_two.iter());
        Self::subroutine(&zipped);
    }
    
    fn subroutine<Arg: Iterator<Item=(&'a u8, &'a u8)>>(_zipped: &Arg) {}
}

fn main() {
    let num = 0u8;
    let mut foo = Foo {
        borrowed: &num,
        owned_one: vec![0],
        owned_two: vec![1],
        output: 0
    };
    foo.do_stuff();
    let _out = &foo.output;
}

However, now compilation fails with:

error[E0502]: cannot borrow `foo.output` as immutable because it is also borrowed as mutable
  --> src/lib.rs:27:16
   |
26 |     foo.do_stuff();
   |     -------------- mutable borrow occurs here
27 |     let _out = &foo.output;
   |                ^^^^^^^^^^^
   |                |
   |                immutable borrow occurs here
   |                mutable borrow later used here

Why has specifying a lifetime for self in the argument list for do_stuff meant that I suddenly can't take the immutable reference to foo later; and what can I do about it?

Upvotes: 2

Views: 53

Answers (1)

Stargateur
Stargateur

Reputation: 26765

writing fn do_stuff(&'a mut self) in this context mean that the lifetime of this borrow of self, must life as long as what this self has borrow borrowed. That very often not what you want.

Your mistake is here fn subroutine<Arg: Iterator<Item=(&'a u8, &'a u8)>>(_zipped: &Arg) {}. Think of lexical meaning of your declaration. Your method return nothing but require a "parent" lifetime ? There is no reason to. The simple solution is simply to introduce a new lifetime for your Item. Like fn subroutine<'i, Arg: Iterator<Item=(&'i u8, &'i u8)>>(_zipped: &Arg) {}. Unless your function return something link to 'a, no lifetime of parent should be here.

Also, it's better to accept IntoIterator it's more generic, and there is no reason to take it by reference and finally when you have such complex generic better use where, and really really if you want to be pedantic you need two lifetime:

fn subroutine<'i, 'j, Arg>(_zipped: Arg)
where
    Arg: IntoIterator<Item = (&'i u8, &'j u8)>,
{
}

Upvotes: 3

Related Questions