Borrow checker thinks double &mut remains borrowed, despite signature

I have a mutable struct and a meta-struct that holds &mut ref to the first one. When I pass the latter to a non-mutable method with owned output (generate_items), the borrow checker thinks the passed struct gets borrowed and forbids the other borrowing call (insert_items).

Playground


struct Calculator { pub cell: usize }
struct MetaCalc<'a> { calc: &'a mut Calculator }
struct Storage { pub data: usize }

impl<'a> Storage {
	fn generate_items(&self, input: &usize, meta: &'a mut MetaCalc<'a>) -> Vec<usize> {
	    println!("input: {input}");
		vec![987, 654, 321]
	}

	fn insert_items(&mut self, input: &Vec<usize>, meta: &'a mut MetaCalc<'a>) {
		for i in input {
			self.data += *i;
		}
	}
}

fn main() {
	let mut calc = Calculator { cell: 1 };
	let mut meta = MetaCalc { calc: &mut calc };
	let mut st = Storage { data: 345 };

	let items = st.generate_items(&987, &mut meta);
    //                                   ^^^^^^^^
    // borrow checker thinks `meta` gets borrowed here and remains so
	st.insert_items(&items, &mut meta);
    // BC complains of this call ^^
	// other structs will use `items`

}

Why? In generate_items, nothing is borrowed -- the output struct is owned, and has no lifetimes with it.

If I remove &mut Calc member from struct Meta and pass it along with other params, the code compiles fine. But it becomes a big pain to work with -- couple of dozen of test calls like this are irritating.

Still, if I make Meta a mutable param (even if it's detached from Calculator), it again becomes "borrowed" and stops to compile.

I thought, it might be because lifetime 'a is shared among two params, but introducing another lifetime won't work, because it's not constrained by anything.

Any suggestions how to work around with it?

1 Like

Here is an explanation of why this specific pattern is pretty much always wrong. Simply changing it to &mut MetaCalc<'a> solves the issue.

6 Likes

Ooooh, that makes sense. I stepped on lifetimes rakes earlier, so I'm familiar with overborrowing. Thanks a lot!

(temporarily unchecked the solution, will put back)

Follow-up question: is there a sane way to have a trait with associated type and avoid this pitfall?

I tried wrapping the assoc.type into another struct, but it same way fall into this trap of 'a Type<'a>. I tried adding another lifetime, but compiler won't allow this, because the new lifetime becomes unconstrained.

Or am I running into some fundamental limitation of traits?

Playgound

trait MyTrait<'a> { // 'a because lifetimes are needed, and the trait won't work without them
    type ExtraData;
    fn do_one(&self, data: usize, extra: Self::ExtraData);
    fn do_two(&self, data: usize, extra: Self::ExtraData);
}

struct DataA(pub usize);
struct DataB<'a>(pub &'a mut DataA);
struct DataC(pub usize);

struct MainStruct(pub usize);
impl<'a> MyTrait<'a> for MainStruct {
    type ExtraData = (&'a mut DataB<'a>, &'a DataC);
    // can't have refs ^^^ without explicit lifetime, but can't add 'b.
    fn do_one(&self, data: usize, extra: Self::ExtraData) { todo!() }
    fn do_two(&self, data: usize, extra: Self::ExtraData) { todo!() }
}

fn main() {
    let ms = MainStruct(123);
    let mut data_a = DataA(456);
    let mut data_b = DataB(&mut data_a);
    let data_c = DataC(789);

	ms.do_one(111, (&mut data_b, &data_c));
	ms.do_two(222, (&mut data_b, &data_c));
}

Why would this be related to traits? It's purely a lifetime error. You still got the literal &'a mut T<'a> anti-pattern, which won't compile regardless of any traits.

If you do what you should and separate the two lifetimes, then of course it compiles inside the trait impl too.

1 Like

Well, I have met small errors that would lead to something fundamental, like self-referential structs.

Thanks for help!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.