0
pub struct Writer<'a> {
    target: &'a mut String,
}

impl<'a> Writer<'a> {
    fn indent<'b>(&'b mut self) -> &'a String {
        self.target
    }
}

Compiling this code results in the following error:

error: lifetime may not live long enough
  --> src/bin/main29.rs:19:9
   |
17 | impl<'a> Writer<'a> {
   |      -- lifetime `'a` defined here
18 |     fn indent<'b>(&'b mut self) -> &'a String {
   |               -- lifetime `'b` defined here
19 |         self.target
   |         ^^^^^^^^^^^ method was supposed to return data with lifetime `'a` but it is returning data with lifetime `'b`
   |
   = help: consider adding the following bound: `'b: 'a`

Why can't this Rust code be compiled? I thought it would work.

1
  • &'a mut String can't be copied, and it can't be moved out of self when self is &mut. Moving self so it is consumed will allow you to move self.target out and avoid the lifetime issue: Playground. (I don't have time to turn this into an answer. Please feel free to yourself if it works for you.) Commented Jan 29, 2024 at 13:54

2 Answers 2

1

Because any data that belongs to a &'b Writer<'a> only lives for the shorter of 'a and 'b', but you're trying to return data that lives for 'a. As the compiler says, this only works if 'b: 'a.

You could fix this by applying the bound 'b: 'a, or more simply writing &'a mut self, but I strongly recommend you not express types of the form &'a T<'a>; the invariance is incredibly painful to work through. A simpler implementation would be

impl Writer<'_> {
    // or better yet, indent(&self) -> &str
    fn indent(&mut self) -> &String {
        self.target
    }
}

This has the advantage that the lifetimes, being anonymous, will correctly be inferred by the compiler. This works because in general, the compiler will prevent you from constructing a &'b Writer<'a> where 'b strictly outlives 'a, since such a type could contain have a dangling reference; therefore there is no need to foist that responsibility onto the function signature as well.

Sign up to request clarification or add additional context in comments.

2 Comments

If I declare Writer#target as an immutable reference, it can still compile successfully. Why is this so?
@BaiTang because shared references are Copy so you can copy a reference which then is independent of the original struct.
0

&mut T is invariant over T. In your case, T being &String. Since both lifetimes are disjoint (i.e &'b mut &'a String) the compiler will not "shorten" the lifetime 'a for the function call. Conversely, it wants 'b to live for atleast as long as 'a.

The other way to look at it is that you pass mutable reference to a function and you get an output reference. Now the compiler requires assurance that while there already is an active reference to the output data, you cannot mutate the same data through some other "path".

Here's an example of what would happen if we ask the compiler to ignore using unsafe

use std::mem::transmute;

pub struct Writer<'a> {
    target: &'a mut String,
}

impl<'a> Writer<'a> {
    fn indent<'b>(&'b mut self) -> &'a String {
        unsafe {
            transmute (&*self.target)
        }
    }
}

fn main() {
    let mut foo_in = String::from("abc");
    let mut foo = Writer {
        target : &mut foo_in
    };
    
    let foo_out = foo.indent();    // ------'a
                                   //        |    
    // mutation while there is     //        |
    // an active reference 'a      //        |                    
    foo.target.push('d');          //        |
                                   //        |
    dbg!(foo_out);                 //        |    
}

With immutable references you don't have to worry because you can have as many overlapping immutable references as you want.

2 Comments

I'm not quite clear about the example you gave: If the return type of the indent function is marked as 'a, then the borrowing of target by foo.indent() would be extended until the dbg output statement. However, there has been a mutable borrowing of foo.target in the middle (foo.target.push('d')). Doesn't this violate the borrowing check rules? Why was it able to be compiled?
You are right. It should not. But I am using unsafe there which makes 'a and 'b disjoint. i.e 'b is not extended to live for as long as 'a

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.