Lifetime issues. Pushing &str to Vec<&'a str>

Hi. I am trying to push an &str to a Vec<&'a str> that is inside a struct that has a lifetime. I am able to push in the &str using a &'static str. I'm wondering if it can be done using a defined lifetime rather than a static lifetime? and if yes, how to go about refactoring my code? if i don't use the static lifetime, I will get following error explicit lifetime required in the type of item lifetime 'a required

fn main() {
   let mut p1 = Person::new("Eddie", 22, true, Gender::Male);

   p1.add_collection("alfa");
   println!("{:?}", p1);
}

struct Person<'a> {
   name: &'a str,
   age: u32,
   verified: bool,
   gender: Gender,
   collection: Vec<&'a str>,
}

impl<'a> Person<'a>{
   fn new(_name: &'a str, _age: u32, _verified: bool, _gender: Gender) -> Self {
      Self {
         name: _name,
         age: _age,
         verified: _verified,
         gender: _gender,
         collection: vec!["toyota", "hyundai"],
      }
   }

    fn add_collection(&mut self, item: &str) {
      self.collection.push(item);
   }
}

You have to declare that item is required to live at least as long as 'a:

fn add_collection(&mut self, item: &'a str) { … }
3 Likes

Due to variance, you can push any lifetime longer than 'a into your collection. The lifetime bound 'x: 'y means "'x outlives 'y", or "'x is valid everywhere 'y is valid (and maybe more)". Thus:

-    fn add_collection(&mut self, item: &str) {
+    fn add_collection<'b: 'a>(&mut self, item: &'b str) {

Playground.

Edit: Also due to variance, this is practically equivalent to @H2CO3's simpler version.

4 Likes

@H2CO3 & @quinedot Thanks a lot! I did this earlier hence it doesn't work fn add_collection<'b>(&mut self, item: &'b str) {} . now i comprehend. appreciate a lot! :smiley:

If you're in the process of learning the Rust language, you may not understand the answers above though they're correct. More practical answer would be that you should not store &'a str in structs or collections like Vec<T>. &T - including &str - is a reference, which means it's a borrowed view to the value owned by someone else. Since you don't own it the language restricts its usage a lot. Instead of the &str, use String to store textual values.

In case you've not read the book yet, its chapter 4 has nice explanation what the ownership is in Rust.

Not directly related to your question, but in Rust prefixing variable name with underscore conventionally means you don't want to use it later. If you remove all the underscores you can use fancy struct field initialization shorthand syntax.

struct Person {
   name: String,
   age: u32,
   verified: bool,
   gender: Gender,
   collection: Vec<String>,
}
...
   fn new(name: String, age: u32, verified: bool, gender: Gender) -> Self {
      Self {
         name,
         age,
         verified,
         gender,
         collection: vec!["toyota".into(), "hyundai".into()],
      }
   }
3 Likes
fn add_collection<'b>(&mut self, item: &'b str) {}

means exactly the same as

fn add_collection(&mut self, item: &str) {}

so changing the latter to the former will never help. Since references always have to know how long the pointed data is valid, if you don't write out the lifetime of a reference in a function declaration, it automatically gets assigned a fresh lifetime variable for convenience, since this is empirically the most frequent and useful thing to do, and removes a lot of noise from function declarations.

However, such lifetime elision is not magic – the compiler cannot possibly guess whether you meant this (e.g. pass in a temporary reference) or what the answers above declare (i.e. pass in a long-enough-living reference).

1 Like

Thanks for your pointers! Appreciate. On the topic of using &str vs String in structs, I thought it's more efficient than using String assuming name and the items in the collection (for my example) will not be mutated?

Yeah. I'm learning Rust :smiley:

Mutabilities are totally different story. I highly recommend to read the chapter I linked, but in short to store references you need to answer the compiler that this value is owned by whom until when. This is hard task to learn like riding bike, so it's not recommended to learn it at the same time to learn the basic language constructs. You don't need it to write practical program in Rust. Don't learn juggling and riding bike simultaneously.

2 Likes

Generally, String vs &str is not really about mutation. It's about ownership. You should make your choice based on whether the string should be owned by the struct or not — choosing based on whether you want your code to be fast or not is not correct.

I mean, think about it: If the only difference was that &str is faster, then why have String in the first place?

4 Likes

Using String is not any less efficient than using str in itself. That's a misconception. There is no difference in how the contents of String and str can be manipulated, searched, printed, spliced, etc. Both types represent a contiguous byte array in UTF-8 format. (And anyway, most of the methods you think are methods on String are actually methods on str, and they come from Deref<Target = str>.)

What can slow down your code is lots of unnecessary cloning and allocations. So when you only need to read from a string and not store it, you might want to make your functions accept a str and not a String so that the caller doesn't need to allocate and/or copy the string buffer. However, even if you only use String and clone it around by default, it will probably be just fine for the most part.

3 Likes

Greatly appreciation for all your responses!! :smiley: They really help me a lot!! Once again, thank you very much

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.