Lifetime problem that should be valid

Hi, I have this snippet of code:

pub struct SomeBuilder<'a>{
    pub arg1: &'a str
    
}

impl<'a> SomeBuilder<'a>{
    pub fn new(arg1: &'a str) -> SomeBuilder<'a>{
        SomeBuilder{
            arg1
        }
    }

    pub fn set_arg(&mut self, arg1: &'a str){
        self.arg1 = arg1;
    }

    pub fn build(self) -> String{
        self.arg1.to_string()
    }
}

fn setter(mut builder: SomeBuilder) -> String{
    let str1 = String::from("asdad");
    builder.set_arg(str1.as_str());
    builder.build()
}

fn main(){
    let builder = SomeBuilder::new("Hello");
    setter(builder);
}

This is just for exemplification as my real usecase is longer and more complex, but essentially the compiler complains about the lifetime of "str1":

23 | fn setter(mut builder: SomeBuilder) -> String{
   |           ----------- has type `SomeBuilder<'1>`
24 |     let str1 = String::from("asdad");
   |         ---- binding `str1` declared here
25 |     builder.set_arg(str1.as_str());
   |     ----------------^^^^----------
   |     |               |
   |     |               borrowed value does not live long enough
   |     argument requires that `str1` is borrowed for `'1`
26 |     builder.build()
27 | }
   | - `str1` dropped here while still borrowed

From my point of view, this should be perfectly valid, the Builder is constructed on the main function, passed by value to the "setter" function, which then resets the argument with a local scope variable. Finally the builder is destroyed at the end of that scope, which should make the code valid.
Instead, the compiler acts as the builder was not destroyed and it was passed back to the main function. Maybe there is a mechanism that I am not aware of, to coerce the lifetime of the builder data to be the same as the function scope.

A setter method necessary implies that the struct outlives its arguments, but your code says that SomeBuilder lives exactly as long as arg1.

This is a common mistake done by people learning Rust.

You just need to do let mut builder = builder at the start of setter, although I agree this feels a bit counterintuitive.

2 Likes

Here is a workaround:

    pub fn set_arg(mut self, arg1: &'a str) -> Self {
        self.arg1 = arg1;
        return self;
    }
fn setter<'a>(mut builder: SomeBuilder<'a>) -> String {
    let str1 = String::from("asdad");
    return builder.set_arg(&str1).build();
}

I don't know if this fits your case or not.
UPDATE: remove <'a> from the setter i forgot them while i was testing

1 Like

I can understand the intention of the code, but what you think "should" be valid is not how rust's lifetime actually works.

the term "lifetime" is an unfortunate misnomer, or at least ambigous. rust's lifetimes are part of the type system, it has different meaning than the timeline of a runtime value like many other languages.

this is the problem. when you create the builder, its type must be determined, and the generic 'a is instantiated with some region in the main funtion.

for this particular example, you can actually "shorten" the lifetime and make the code compile, but you must do it explicitly. however, this may not be possible in the general case:

impl<'a> SomeBuilder<'a>{
    pub fn shorten<'b>(self) -> SomeBuilder<'b> where 'a: 'b {
        SomeBuilder {
            arg1: self.arg1
        }
    }
}
fn setter(builder: SomeBuilder) -> String{
    let mut builder = builder.shorten();
    let str1 = String::from("asdad");
    builder.set_arg(str1.as_str());
    builder.build()
}

EDIT:

because the builder is covariant over the lifetime, the shorten() method can be implemented by "coercing" self, which explains what a simple "rebind" is doing under the hood:

impl<'a> SomeBuilder<'a>{
    pub fn shorten<'b>(self) -> SomeBuilder<'b> where 'a: 'b {
        self
    }
}
2 Likes

@x0rw That actually solves my problem, but I don't understand why does this work and mine example doesn't.

@SkiFire13 The builder needs to be constructed outside of that function for my particular usecase.

@nerditation Thanks, that also works but it doesn't fell like a correct way to do things.

My plan was to make a Builder struct that can be used to construct a more complex type, since at building time I may need to copy/clone values around multiple times, I thought that using references would be a much more performant way to accomplish it but now i can see some tradeoffs regarding usability.
Do you think this is the correct thought process or should instead go trough the easy path and store things by value?

I don't see how my suggestion prevents that, it's just a matter of adding a line inside the setter function, the caller remains like it was before:

  fn setter(mut builder: SomeBuilder) -> String{
+     let mut builder = builder;
      let str1 = String::from("asdad");
      builder.set_arg(str1.as_str());
      builder.build()
  }
1 Like

Generally in the builder pattern you want the setters to own your struct to enable chaining methods and avoid borrowing problems,
in the code i posted just remove the lifetime parameter <'a>, i forgot it while testing

1 Like

As @nerditation said, Rust lifetimes (those 'a things) aren't about the liveness scope of values. They're generally about the duration of borrows. To hopefully explain the original error:

// Written without lifetime elision
fn setter<'a>(mut builder: SomeBuilder<'a>) -> String {

The caller chooses the lifetime in this case.[1] They could choose 'static, for example.[2] You can't borrow a local variable for every possible lifetime they may choose: going out of scope at the end of the function will conflict with being borrowed. And that's why the error triggered: not because it thought a value wasn't destroyed, but because the borrow was required to be too long.

The solution is to make the borrow duration -- the lifetime -- shorter. In this case, SomeBuilder<'short> is a supertype of SomeBuilder<'long>, so this is possible without unsafe or the like. You've just hit a case where, unfortunately, the coercion to a supertype isn't automatic.[3]

The workarounds either provide a place where the coercion does happen (an assignment, a call to set_arg by value) or is spelled out explicitly (shorten).

Well, you've identified that not borrowing is generally easier. Whether a borrowing approach is sensible or feasible depends on the use case. If the built type is going to ultimately own the values, I'd probably have the builder own them too. A builder that must borrow will prevent certain usage patterns, like returning the builder or passing it to another non-scoped thread. You perhaps could give them the choice with different builders or something like Cow<'_, str>, but it may not be worth the complexity. Especially if building is just an occasional thing.

In case it's not clear, these are all the same signature:

fn setter<'a>(mut builder: SomeBuilder<'a>) -> String {
fn setter(mut builder: SomeBuilder<'_>) -> String {
fn setter(mut builder: SomeBuilder) -> String {

I prefer the middle one as knowing when a borrow is present is generally useful information.[4]


  1. a parameter on a function ↩︎

  2. They aren't even able to choose a lifetime shorter than the call to the function, actually. ↩︎

  3. The coercion can't happen at the call to set_arg that takes &mut self, because &mut SomeBuilder<'long> cannot coerce to &mut SomeBuilder<'short>. And the OP has no uses of builder between the declaration with the too-long lifetime and the call to set_arg. ↩︎

  4. Though it's limited in this case, in contrast with a lifetime that shows up in return position. ↩︎

2 Likes

Thanks a lot for all the answers, this has generally change my view on Builders and why setters should own the values.

Sorry @SkiFire13, for some stupid reason I did not read the answer correctly, I see what you mean now.

Let me put my two cents in. The other guys have already explained it but I'd love to share my thoughts (maybe explaining it will help me understand this better :grinning_face:)

In the main function you create an instance of SomeBuilder which toughly looks like this (lifetime annotation included)

let builder: /* SomeBuilder<'1>*/ = SomeBuilder::new("Hello");

Then in the setter you call builder.set_arg(str1.as_str()); which has the following signature

pub fn set_arg(&mut self, arg1: &'a str)

If we replace the generic lifetime 'a with the one your instance of somebuilder has then we really have the following signature:

pub fn set_arg(&mut self, arg1: &'1 str)

Going back to the setter function. The signature of that function is:

fn setter(mut builder: SomeBuilder<'1>)

When you call the builder.set_arg(str1.as_str()); the compiler thinks the that str1.as_str() lives a long as lifetime '1 but that reference is borrowed for a lifetime of, let's call it 'str1, which the lifetime of the region inside the setter function. 'str1 is shorter than '1 hence you get the error message you see. This is based on the variance rules the compiler follows

&'a str is covariant over 'a. Which means that you can pass &'static str ('static <:'a) but not &'b str where 'b shorter than 'a. You can test it yourself:

fn setter(mut builder: SomeBuilder) -> String{
    builder.set_arg("asdad"); // &'static str
    builder.build()
}

Now why does @SkiFire13 solution work? Let's try to see what the compiler does behind the scene.

fn setter(builder: SomeBuilder) -> String{
    let mut builder = builder;
    let str1 = String::from("asdad");
    builder.set_arg(&str1);
    builder.build()
}

More specifically this assignment

let mut builder = builder;

This works based on the same variance rules.

let mut builder: SomeBuilder<'str1> = builder /*SomeBuilder<'1>*/

where lifetime '1 (as a longer one) can be assigned to a 'str1 which is shorter.