Seeking advice on string lifetime in a struct Vec<&str>

As a simplified case (provided in the code below), I have a Config struct which holds two fields: active and disabled of the type vector of strings meant to capture active and disabled configuration options.

A disable operation simply removes a string from active and adds it to disabled. A symmetrical enable operation (not included below) does the opposite.

If I uncomment self.disabled.push(name); below I get the following error:

error: lifetime may not live long enough
  --> demo.rs:23:13
   |
21 |     pub fn disable(&mut self, names: Vec<&str>) {
   |                    ---------             - let's call the lifetime of this reference `'1`
   |                    |
   |                    has type `&mut Config<'2>`
22 |         for name in names {
23 |             self.disabled.push(name);
   |             ^^^^^^^^^^^^^^^^^^^^^^^^ argument requires that `'1` must outlive `'2`

error: aborting due to 1 previous error

How should one handle such a case? The compiler, which is typically helpful, does not offer any advice on how to resolve this. Should I switch to a String type even thought the default options for both active and disabled are known at compile time as provided in the new constructor method ?

#[derive(Debug)]
pub struct Config<'a> {
    pub name: String,
    pub version: String,
    pub active: Vec<&'a str>,
    pub disabled: Vec<&'a str>,
}

impl Config<'_> {
    pub fn new(name: String, version: String) -> Self {
        Self {
            name: name.clone(),
            version: version.clone(),
            active: vec!["a", "b", "c"],
            disabled: vec!["d", "e", "f"],
        }
    }

    pub fn disable(&mut self, names: Vec<&str>) {
        for name in names {
            // self.disabled.push(name);
            self.active.retain(|&x| x != name);
        }
    }
}

fn main() {
    let mut cfg = Config::new(
        "foo".to_string(), 
        "1.1.0".to_string()
    );
    println!("cfg before is {:?}", cfg);
    cfg.disable(vec!["a", "c"]);
    println!("\ncfg after is {:?}", cfg);
}

If they're always literals, you could use &'static str. Or consider an enum (and perhaps some parsing) so that you have the benefits of being strongly typed instead of stringly typed.

If they're not always literals, String is probably the correct choice. Even if using the lifetime is possible, it's probably an unnecessary complication. Some configuration shuffling is unlikely to be the slowest part of your application.


To answer the actual code question though, if you want to push &strs into your Vec<&'a str>, your method should be taking &'a strs, not &strs with a caller-chosen lifetime that could be arbitrarily shorter than 'a.

//  vvvv        vv
impl<'a> Config<'a> {
    //                                    vvv
    pub fn disable(&mut self, names: Vec<&'a str>) {
        for name in names {
            self.disabled.push(name);
            self.active.retain(|&x| x != name);
        }
    }
}

Perhaps watch this video It does a good job of explaining lifetime elision, and how that results in distinct lifetimes, and how that means that you're trying to push the wrong type into a Vec.

Here's what your code meant with the elision:

impl<'a> Config<'a> {
    //                                    
    pub fn disable<'m, 'b>(self: &'m mut Config<'a>, names: Vec<&'b str>) {

So you were trying to put a &'b str in a Vec<&'a str>, but those aren't the same type.

When you need generic types (including those which are generic due to a lifetime) to be the same, you have to sort of thread that information through your code, by giving the generic parts the same name (in this case, 'a).

4 Likes

@quinedot

Thanks very much for your comprehensive help and advice which is very well received, especially the general guidance on which string types to use and how to propagate lifetime annotations.

I have included the working example below based on your solution which works great:

#[derive(Debug)]
pub struct Config<'a> {
    pub name: String,
    pub version: String,
    pub active: Vec<&'a str>,
    pub disabled: Vec<&'a str>,
}

impl<'a> Config<'a> {
    pub fn new(name: String, version: String) -> Self {
        Self {
            name: name.clone(),
            version: version.clone(),
            active: vec!["a", "b", "c"],
            disabled: vec!["d", "e", "f"],
        }
    }

    pub fn disable(&mut self, names: Vec<&'a str>) {
        for name in names {
            self.disabled.push(name);
            self.active.retain(|&x| x != name);
        }
    }

    pub fn enable(&mut self, names: Vec<&'a str>) {
        for name in names {
            self.active.push(name);
            self.disabled.retain(|&x| x != name);
        }
    }
}

fn main() {
    let mut cfg = Config::new(
        "foo".to_string(), 
        "1.1.0".to_string()
    );
    println!("cfg before is {:?}", cfg);
    cfg.disable(vec!["a", "c"]);
    println!("\ncfg after is {:?}", cfg);
    cfg.enable(vec!{"a"});
    println!("\nfinally: {:?}", cfg);
}
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.