workerjoe
workerjoe

Reputation: 2663

How to use lifetimes when capturing a &str in a builder pattern?

I am working on a random text generator which is going to store a "pattern" for output (among other parameters), and I think &str is the right data type given that the user of my library will specify the pattern in his code. I'm using a "Builder" pattern to build a "Generator" struct, and I want to specify that the pattern will outlive both the builder and generator. Here's my code without lifetime parameters:

pub struct Generator {
    pub pattern: &str,
}

impl Generator {
    pub fn builder() -> GeneratorBuilder {
        GeneratorBuilder::new()
    }
}

pub struct GeneratorBuilder {
    pattern: &str,
}

impl GeneratorBuilder {
    pub fn new() -> Self {
        Self {
            pattern: "",
        }
    }
    pub fn with_pattern(mut self, pattern: &str) -> Self {
        self.pattern = pattern;
        self
    }
    pub fn build(&self) -> Generator {
        Generator {
            pattern: self.pattern,
        }
    }
}

The compiler at this point tells me I need lifetime specifiers in both struct definitions, and if I follow its recommendations I end up with 'a and '_ and 'static at various places throughout my code. But this will not compile. The code now is:

pub struct Generator<'a> {
    pub pattern: &'a str,
}

impl Generator<'_> {
    pub fn builder() -> GeneratorBuilder<'static> {
        GeneratorBuilder::new()
    }
}

pub struct GeneratorBuilder<'a> {
    pattern: &'a str,
}

impl GeneratorBuilder<'_> {
    pub fn new() -> Self {
        Self {
            pattern: "",
        }
    }
    pub fn with_pattern(mut self, pattern: &str) -> Self {
        self.pattern = pattern;
        self
    }
    pub fn build(&self) -> Generator {
        Generator {
            pattern: self.pattern,
        }
    }
}

The error I end up with is "lifetime may not live long enough" in the definition of function with_pattern because "assignment requires that [pattern] must outlife [self]". My question is: Where do I need lifetime specifiers, and which ones do I need, to tell Rust that the str called "pattern" will live longer than the Generator and the GeneratorBuilder?

Upvotes: 1

Views: 229

Answers (2)

Jmb
Jmb

Reputation: 23359

You need to be more detailed on which lifetimes are used at each stage. In particular you need to:

  • Tell the compiler that Generators created with the default pattern have a 'static lifetime. This is done by having new return GeneratorBuilder<'static>
  • Tell the compiler which lifetime is used for the created Generator in GeneratorBuilder::build. There are two possibles, either the lifetime of the &'_ self reference or the lifetime embedded in the GeneratorBuilder<'_>. By default the compiler takes the lifetime of the reference but you want the other one: fn build<'b>(&'b self) -> Generator<'a> (the 'b lifetime can actually be omitted here, I've just added it to make the distinction more explicit).

Complete working example:

#[derive (Debug)]
pub struct Generator<'a> {
    pub pattern: &'a str,
}

impl<'a> Generator<'a> {
    pub fn builder() -> GeneratorBuilder<'a> {
        GeneratorBuilder::new()
    }
}

pub struct GeneratorBuilder<'a> {
    pattern: &'a str,
}

impl<'a> GeneratorBuilder<'a> {
    pub fn new() -> GeneratorBuilder<'static> {
        GeneratorBuilder {
            pattern: "default",
        }
    }
    pub fn with_pattern(mut self, pattern: &'a str) -> Self {
        self.pattern = pattern;
        self
    }
    pub fn build(&self) -> Generator<'a> {
        Generator {
            pattern: self.pattern,
        }
    }
}

fn main() {
    let g = GeneratorBuilder::new().build();
    println!("{g:?}");
    
    let g = GeneratorBuilder::new().with_pattern ("foobar").build();
    println!("{g:?}");
}

Playground

Upvotes: 2

PitaJ
PitaJ

Reputation: 15074

What you need to do is just use 'a everywhere:

pub struct Generator<'a> {
    pub pattern: &'a str,
}

impl<'a> Generator<'a> {
    pub fn builder() -> GeneratorBuilder<'a> {
        GeneratorBuilder::new()
    }
}

pub struct GeneratorBuilder<'a> {
    pattern: &'a str,
}

impl<'a> GeneratorBuilder<'a> {
    pub fn new() -> Self {
        Self {
            pattern: "",
        }
    }
    pub fn with_pattern(mut self, pattern: &'a str) -> Self {
        self.pattern = pattern;
        self
    }
    pub fn build(&self) -> Generator {
        Generator {
            pattern: self.pattern,
        }
    }
}

Upvotes: 1

Related Questions