0

I'm trying to use the &str type in one of my struct. this struct has an async new method so I can read the parameter from the env file and create an instance. the problem is I can't fix the lifetime of it.

pub struct AppContext<'a>{
    pub site_url: &'a str
}
impl<'a> AppContext<'a>{
    pub async fn new()->AppContext<'a>{
        //fill website url base on env variables
        let site_url =  std::env::var("WEBSITE_URL").unwrap_or("http://localhost:3000".to_string());
        AppContext{
            site_url: site_url.as_str()
        }
    }
}

it keep telling me cannot return value referencing local variable 'site_url' returns a value referencing data owned by the current function how can I fix this?

javad bat
  • 4,236
  • 6
  • 26
  • 44
  • 4
    Store `String` and not `&str`. In fact, while you're a beginner, my recommendation is to avoid lifetimes. Only store owned types in structs. This will require you to clone sometimes, but this is OK. – Chayim Friedman Jul 27 '23 at 07:52

2 Answers2

1

If you were allowed to do this, it would result in a use-after-free error. Note that site_url has type String. The function String::as_str takes a &'a String and outputs a &'a str. In other words, the reference to the str is only valid for as long as the reference to the underlying String is.

Since site_url is a local variable, it will go out of scope at the end of the block where it's defined. This means that the &site_url which is used in site_url.as_str() is only valid until that time. After that point, the string will be freed.

    pub async fn new()->AppContext<'a>{
        //fill website url base on env variables
        let site_url =  std::env::var("WEBSITE_URL").unwrap_or("http://localhost:3000".to_string());
        AppContext{
            site_url: site_url.as_str()
        }
        // site_url is a local variable that goes out of scope here. This is 
        // when site_url will be dropped, and the reference to it will no longer be valid
    }

This means that by the time you're actually using the AppContext that the new function eventually produces, the &'a str inside it will be invalid!

One solution is to redefine AppContext so site_url is a String, not a &str.

Mark Saving
  • 1,752
  • 7
  • 11
  • sure, this is a way but I prefer &str over string if possible. if there is no other way to do this I have to change it's type as you say – javad bat Jul 27 '23 at 09:01
  • 1
    @javadbat If you really want to deal with `&str`, then pass the value in as a parameter of `new`. – PatientPenguin Jul 27 '23 at 09:11
1

I believe this code will work for you.

Your AppContext will still work for normal reference to str, but it will also support smart pointers like String which own their values.
This allows AppContext to be either owner or borrower of str. You will always choose the more convenient option.


    use std::ops::Deref;
    
    #[derive(Debug)]
    pub struct AppContext<D: Deref<Target = str>> {
        pub site_url: D,
    }
    
    impl<D: Deref<Target = str>> AppContext<D> {
        pub fn new(site_url: D) -> Self {
            Self { site_url }
        }
    }
    
    fn get_website_url() -> String {
        std::env::var("WEBSITE_URL").unwrap_or_else(|_| "http://localhost:3000".to_string())
    }
    
    fn main() {
        let owning_obj = AppContext::new(get_website_url());
        let dependant_obj = AppContext::new("https://Your/hardcoded/page.com");
        dbg!(owning_obj, dependant_obj);
    }

Also, you may consider type erasure with Box<dyn Deref<str>> to make AppContext non-generic.

Siiir
  • 210
  • 6
  • I corrected my code. I tried to add `DerefMut`, but it would make it impossible to use string literals. – Siiir Jul 27 '23 at 09:17
  • Note that `AppContext` should not require the `Deref` bound on the struct. See [this answer](https://stackoverflow.com/a/66369912/6655004) for more details. – Filipe Rodrigues Jul 28 '23 at 14:40