Another lifetimes + mutable reference question
⚓ Rust 📅 2025-10-30 👤 surdeus 👁️ 6I'm working on something rather complex, and have been running into a problem I can't seem to fully resolve. This is not a new problem, plenty of specific questions here and on SO, but so far I have found nothing that explains to me how to solve or avoid this (seeming) anti-pattern in general.
Long story short, I'm doing complex things and would like to cache the results. Unfortunately, part of the complex things involve an external library that also does complex things and returns its results through a struct with a lifetime parameter. That means if I want to cache those, my cache struct now also needs a lifetime parameter. And suddenly everything breaks.
Here's a simplified example, with every extraneous thing omitted.
First, the before-times. When all was well.
#[derive(Debug)]
struct MyThing(String);
#[derive(Debug)]
struct StructWithoutLifetime {
my_stuff: Vec<MyThing>,
}
impl StructWithoutLifetime {
fn very_complex_function_to_process_my_thing(&mut self, thing: MyThing) {
// Such complex, much work
self.my_stuff.push(thing);
}
fn process_lots_of_things(&mut self, things: Vec<MyThing>) {
for thing in things {
self.very_complex_function_to_process_my_thing(thing);
}
}
}
fn main() {
let mut struct_without_lifetime = StructWithoutLifetime {
my_stuff: Vec::new(),
};
struct_without_lifetime.very_complex_function_to_process_my_thing(MyThing("one".to_string()));
println!("{:?}", struct_without_lifetime);
}
This works quite nicely.
Next, I introduce the external library, and the entire world explodes.
// Imagine the external library function that creates these is very expensive,
// so I'd like to store them instead of recalculating them
#[derive(Debug)]
struct FromExternalLibrary<'a> {
nr: &'a i32
}
#[derive(Debug)]
struct MyThing(String);
#[derive(Debug)]
struct StructWithLifetime<'a> {
my_stuff: Vec<MyThing>,
external_library_stuff: Option<FromExternalLibrary<'a>>
}
impl<'a> StructWithLifetime<'a> {
fn very_complex_function_to_process_my_thing(&'a mut self, thing: MyThing) {
// Such complex, much work
self.my_stuff.push(thing);
}
fn process_lots_of_things(&'a mut self, things: Vec<MyThing>) {
for thing in things {
self.very_complex_function_to_process_my_thing(thing);
}
}
}
fn main() {
let mut struct_with_lifetime = StructWithLifetime {
my_stuff: Vec::new(),
external_library_stuff: None
};
struct_with_lifetime.very_complex_function_to_process_my_thing(MyThing("one".to_string()));
println!("{:?}", struct_with_lifetime);
}
Boom:
error[E0499]: cannot borrow `*self` as mutable more than once at a time
--> src/main.rs:25:13
|
17 | impl<'a> StructWithLifetime<'a> {
| -- lifetime `'a` defined here
...
25 | self.very_complex_function_to_process_my_thing(thing);
| ^^^^-------------------------------------------------
| |
| `*self` was mutably borrowed here in the previous iteration of the loop
| argument requires that `*self` is borrowed for `'a`
error[E0502]: cannot borrow `struct_with_lifetime` as immutable because it is also borrowed as mutable
--> src/main.rs:38:22
|
36 | struct_with_lifetime.very_complex_function_to_process_my_thing(MyThing("one".to_string()));
| -------------------- mutable borrow occurs here
37 |
38 | println!("{:?}", struct_with_lifetime);
| ^^^^^^^^^^^^^^^^^^^^
| |
| immutable borrow occurs here
| mutable borrow later used here
|
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
Now, in my actual case, it would be quite a lot of work to refactor the two methods in my struct. But suppose I do so anyway, and end up with something like this:
// Imagine the external library function that creates these is very expensive,
// so I'd like to store them instead of recalculating them
#[derive(Debug)]
struct FromExternalLibrary<'a> {
nr: &'a i32
}
#[derive(Debug)]
struct MyThing(String);
#[derive(Debug)]
struct StructWithLifetime<'a> {
my_stuff: Vec<MyThing>,
external_library_stuff: Option<FromExternalLibrary<'a>>
}
impl<'a> StructWithLifetime<'a> {
fn process_any_amount_of_things(&'a mut self, things: Vec<MyThing>) {
for thing in things {
// Such complex, much work
self.my_stuff.push(thing);
}
}
}
fn main() {
let mut struct_with_lifetime = StructWithLifetime {
my_stuff: Vec::new(),
external_library_stuff: None
};
struct_with_lifetime.process_any_amount_of_things(vec![MyThing("one".to_string())]);
println!("{:?}", struct_with_lifetime);
}
(playground)
This solves one problem, as I'm no longer borrowing in a loop. However, the error in main remains:
error[E0502]: cannot borrow `struct_with_lifetime` as immutable because it is also borrowed as mutable
--> src/main.rs:34:22
|
32 | struct_with_lifetime.process_any_amount_of_things(vec![MyThing("one".to_string())]);
| -------------------- mutable borrow occurs here
33 |
34 | println!("{:?}", struct_with_lifetime);
| ^^^^^^^^^^^^^^^^^^^^
| |
| immutable borrow occurs here
| mutable borrow later used here
|
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
It's as if as soon as a lifetime parameter is introduced, that means that once you mutable borrow a struct it can never be borrowed again in the same scope?
As I said before, this is definitely not the first time this kind of question gets asked, either here or on SO, but every time the answer concerns some trivial "gotcha", or consists of "here's specific code to make this specific example work." But fundamentally, I still don't really get the problem here. I kind of have an idea about what's going wrong in the loop (but still don't really know why adding a lifetime bound triggers it), but I have no idea what is going wrong in the main function.
I tried wrapping the mutating process... call inside its own { } scope, screaming at the compiler NO WE'RE DONE MUTATING IT'S FINE. But he's just not having it.
Or is this the part where I'm supposed to just give up and slap a RefCell on all the fields in my struct and whistle past the compiler going "noo officer, only immutable borrows here, yessir, definitely nothing getting mutated in this here function!"
2 posts - 2 participants
🏷️ Rust_feed