Search code examples
rustreferencelifetime

Can I modify an &'a str on a struct instance in rust so that it has the same &'a str lifetime after modification?


struct test<'a> {
    name: &'a str
}

impl<'a> test<'a> {
    fn modify_name(&'a mut self) {
        let new_name = self.name.replacen("az", "", 1);
        // i want to add this name to self with the same lifetime as self
        self.name = &'a new_name[..];
    }
}

Assigning lifetimes like above is not allowed and if I do not specify the lifetime of my str, rust tries to drop it after the function is over. Can not dereference self.name either since "str can not be known at compile time.

I tried looking online but only found the suggestion to use String instead of &str.

I would like to know whether modifying a referenced struct field can be done or not. (I use a lot of &str in my struct and rarely modify the them so I would prefer to have &str's)

Not sure if this is possible. If it is not, please explain to me, what am I missing?


Solution

  • The only way1 you can do this without changing the definition of test is to leak a String. This gives you a reference with 'static lifetime, which will be automatically coerced to any shorter lifetime.

    However, this is obviously less than ideal as the heap allocation will never be freed until the program terminates. If you were to run this function in a loop you would be able to observe the memory usage of your program increasing over time, and it would never decrease.

    fn modify_name(&'a mut self) {
        self.name = self.name.replacen("az", "", 1).leak();
    }
    

    Every time you see & in Rust, it means "this value borrows from something that lives somewhere else." In your case, you want to borrow from a new string, but that means you need somewhere to put the string that will live at least as long as 'a but there is no place this function could possibly put the string that satisfies that constraint -- other than on the heap as a leaked allocation.

    To do this without leaking memory, you need to change the type of name.

    One possible solution would be to use Cow, which represents a value that can be either borrowed or owned. In this example, after calling test::modify_name, the name field will be Cow::Owned regardless of whether it was previously borrowed or owned.

    use std::borrow::Cow;
    
    struct test<'a> {
        name: Cow<'a, str>
    }
    
    impl<'a> test<'a> {
        fn modify_name(&'a mut self) {
            self.name = self.name.replacen("az", "", 1).into();
        }
    }
    

    Note that, even after name becomes owned, it still cannot outlive the lifetime 'a in this example.


    1 Okay, there's one other way, but it's significantly more complicated because it involves passing in a reference to some kind of String store that outlives 'a. The internals of that type will likely require unsafe code.