3

I'm trying to implement a simple builder but struggling with lifetimes. The following is giving error: borrowed value does not live long enough. This question seems similar. If I store t in a mutable variable and then call s and finalize it works, but I want to get the one liner to work. What am I doing wrong?

struct Type<'a> {
    s: &'a String,
}

struct TypeBuilder {
    s: String,
}

impl TypeBuilder {
    fn new() -> TypeBuilder {
        TypeBuilder { s: "".to_string() }
    }

    fn s(&mut self, s: String) -> &mut TypeBuilder {
        self.s = s;
        self
    }

    fn finalize(&self) -> Type {
        Type { s: &self.s }
    }
}

fn main() {
    let t = TypeBuilder::new()
                    .s("a".to_string())
                    .finalize();
    println!("string: {}", t.s);
}
Community
  • 1
  • 1
anderspitman
  • 9,230
  • 10
  • 40
  • 61
  • I asked a concise Question for this common rust error `error[E0716]` [error E0716: temporary value dropped while borrowed (rust)](https://stackoverflow.com/questions/71626083/error-e0716-temporary-value-dropped-while-borrowed-rust). It links back to this Question. – JamesThomasMoon Mar 26 '22 at 07:23

1 Answers1

12

The problem is that you're creating Type with a string slice based on a String from TypeBuilder, but TypeBuilder instance created with new() is destroyed immediately in the same let statement, so if this was allowed, the string slice would became dangling. And that's why it works when you store TypeBuilder in a variable first.

The problem with your approach to the builder is that the builder is the owner of data for the value it builds: Type references the contents of TypeBuilder. This means that Type instances are always tied to TypeBuilder instances, and you just cannot create Type and drop TypeBuilder. However, this is really unnatural - builders are usually transient objects which are only necessary during construction.

Consequently, in order for the builder pattern to work correctly your Type must become the owner of the data:

struct Type {
    s: String,
}

Then the builder should be passed by value and then consumed by finalize():

impl TypeBuilder {
    fn new() -> TypeBuilder {
        TypeBuilder { s: "".to_string() }
    }

    fn s(mut self, s: String) -> TypeBuilder {
        self.s = s;
        self
    }

    fn finalize(self) -> Type {
        Type { s: self.s }
    }
}

This way your building code should work exactly as it is.

Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296
  • 1
    Awesome, thank you. That's actually similar to what I started with, but I was having problems, I think because I was using ``&mut self`` in my modifier functions, instead of consuming with ``mut self``. – anderspitman Apr 06 '15 at 09:33
  • Do you know why they use ``&mut self`` [here](http://doc.rust-lang.org/1.0.0-beta/book/method-syntax.html#builder-pattern)? – anderspitman Apr 06 '15 at 09:36
  • 5
    The approach with `&mut self`/`&self` works there because the builder there only works with `Copy` data, and the structure built by the builder does not contain references into the builder. It won't work for `String`s and other kinds of owned non-copyable data. – Vladimir Matveev Apr 06 '15 at 09:38