Constructor for a struct containing references

I'm sorry, it's probably was answered a lot of times, but I can't google it for some reason. Here's what I'm trying to do.

struct S<'a>(&'a str);

impl<'a> S<'a> {
    fn new() -> Self {
        let s = format!("foo");
        S(&s) // returns a value referencing data owned by the current function
    }
}

I understand that as new owns s it's borrow can't be returned. It would work with a static string if I remove format!, but what to do if the string should be generated at runtime?

If you want to generate the string dynamically inside the constructor, then you simply can't return a reference to it. There is no way around that. If you have a dynamically-formatted String to begin with, you can just store that by value inside S.

3 Likes

As you've surmised, the compiler is complaining because the s variable is only alive inside the S::new() constructor, but we need to make sure it lives for longer so the resulting S can be useful.

The solution is to make sure either a) S owns the string instead of giving it a reference, or b) the string comes from outside of the function so that S won't outlive it.

struct S(String);

impl S {
  fn new() -> Self { S(format!("foo") }
}

or

struct S<'a>(&'a str);

impl<'a> S<'a> {
  fn new(text: &'a str) -> Self { S(text) }
}

fn main() {
  // create a string which is alive for the duration of main()
  let some_string = format!("foo");
  // Note: `s` is not allowed to outlive `some_string`
  let s = S::new(&some_string);  
}

If you are generating the strings at runtime you'll use the first solution 99% of the time.

2 Likes

Given the signature fn new() -> Self, you may want to instead implement the Default trait. It's the canonical way to define a default constructor, and other code can compose well with the trait. You can often derive the implementation, as noted in the documentation (but not when you have a reference in your struct).

If you're sure you want to store &str, and you still want a default constructor (Default implementation), you could do something like:

impl Default for S<'static> {
    fn default() -> Self {
        // The literal string "foo" lives for the entire
        // lifetime of the program ('static)
        S("foo")
    }
}

(If you're not sure that you need &str, you probably want to go with String, as the others noted.)

1 Like