Code design to avoid multiple mutable reference

I have a type like this:

struct Foo<'a> {
    i: Option<&'a mut i32>,
}

impl<'a> Foo<'a> {
    pub fn add_i(&mut self, i: &'a mut i32) {
        self.i = Some(i);
    }
    pub fn consume_i(&mut self) {
        let i = self.i.take().unwrap();
        *i = *i + 1;
    }
}

The idea is that the Foo keeps a mutable reference, and when calling consume_i, it do not own any reference. But the code does not work:

    let mut i = 0;
    let mut foo = Foo {
        i: None
    };
    foo.add_i(&mut i);
    foo.consume_i();
    foo.add_i(&mut i);
    foo.consume_i();

The code will report cannot borrow i as mutable more than once at a time error. And it is the same problem of "lifetime checker do not aware of clear() of a vector". But the solution in the link does not fit very well in my code.

Is there any way to change the API of Foo as minimum as possible, to let the user write codes like above?

P.S. in the real case, create of Foo is very expansive and a solution of "drop and re-create Foo" is not acceptable.

  1. I think we both agree the following code should not compile:
struct Foo<'a> {
    i: Vec<&'a mut i32>,
}

impl<'a> Foo<'a> {
    pub fn add_i(&mut self, i: &'a mut i32) {
      self.i.push(i);
    }
    pub fn consume_i(&mut self) {
    }
}

pub fn main() {
    
        let mut i = 0;
    let mut foo = Foo {
        i: vec![]
    };

    foo.add_i(&mut i);
    foo.consume_i();

    foo.add_i(&mut i);
    foo.consume_i();

}
  1. Now, note, the above has identical signatures for add_i / consume_i as your function above.

  2. A compiler that operates on only the type signature of functions (and not their content) would not be able to distinguish your impl from my impl, and since my impl can't compile, it's unlikely yours will.

2 Likes

I found a solution, force cast the foo with another lifetime:

pub fn consume_i<'b>(&mut self) -> Foo<'b> {
        let i = self.i.take().unwrap();
        *i = *i + 1;
        unsafe { std::mem::transmute(self) }
    }

And as I mentioned, I do want to know if there are other hacks that use unsafe codes within the API, to let the user use the same foo.

Yes, it won't compile. I'm just looking for designs that changes my API as little as possible, while easy to use.

You should avoid having references in structs as much as possible. In this case you could divide it into a normal struct owns its data, and a temporary view that refers to the data stored elsewhere:

struct Foo { /* no references here */ }
struct FooWithData<'a, 'f> { data: &'a mut i32, foo: &'f mut Foo }

let foo = Foo;

let foo_loaded = foo.with(&mut i);
foo_loaded.consume();
let foo_loaded = foo.with(&mut i);
foo_loaded.consume();

The hard part here is that you can't have Foo and FooWithData stored in a struct together, since that would be self-referential.

3 Likes

This is suspicious. What it's saying is that your i variable's lifetime is exactly equal to the &'a mut stored in Foo. Unlike immutable references, mutable references are "invariant" so the compiler doesn't have the wiggle room to shorten/lengthen lifetimes so they match.

Instead of recreating Foo from scratch, what about using update syntax to create a new Foo with a nicer lifetime like this?

impl<'a> Foo<'a> {
  fn with_i<'b>(self, i: &'b mut i32) -> Foo<'b> {
    Foo { i, ..self }
  }
  
  fn consume<'c>(self) -> Foo<'c> {
    Foo { i: None, ..self }
  }
}
3 Likes

Did you mean mut self (vs &mut self)?

If your data structure is covariant (the example is), you might as well return Foo<'static>.


Edit: Oop, just noticed @Michael-F-Bryan already suggested the following.

You said you didn't want to rebuild it, but that's probably exactly what I would do to avoid the unsafe.

    pub fn extend_lifetime(self) -> Foo<'static> {
        Foo {
            i: None,
            ..self
        }
    }

    pub fn consume_i(mut self) -> Foo<'static> {
        let i = self.i.take().unwrap();
        *i = *i + 1;
        self.extend_lifetime()
    }

If you ever add another lifetime-carrying field, this would need adjusting -- and the transmute would be unsound.

Mutable references are still covariant over their lifetimes. They're invariant over their referent type (and any lifetimes contained in it).

5 Likes

True and important -- but in case that confuses someone, note that 'a is invariant in the example as it's part of Self in the &mut self.

2 Likes