Lifetime and static struct

Now that async await is coming near to completion, I have been looking more at rust with web frameworks.

Here is a simple blog app I'm trying to create using tide but seems like I'm fighting with the 'static lifetime.

  • While I do understand that static lifetime lasts as long as the program and most webapps would be the same, why would tide use static lifetime of app_state? Isn't there other annotations that would work here?
  • For BlogOptions would str or String be preferred? When would I choose one over the other. I'm not liking the fact that I need to annotate str with 'a. It seems to make the code more verbose.
  • Based on the current code, how do I make BlogOptions and BlogOptions.path have static lifetime in main func.
main.rs
#![feature(async_await)]

mod blog;

use blog::{Blog, BlogOptions};

use std::env;
use std::io;

fn main() -> Result<(), io::Error> {
    let port = env::var("PORT")
        .ok()
        .and_then(|p| p.parse().ok())
        .unwrap_or(8080);

    let path = env::var("PATH")
        .ok()
        .unwrap_or("./".into());

    let blog = Blog::new(&BlogOptions { path: path.as_str() });
    blog.run(format!("0.0.0.0:{}", port))?;
    Ok(())
}
blog.rs
use std::io;
use std::net::ToSocketAddrs;

pub struct Blog<'a> {
    options: &'a BlogOptions<'a>,
}

pub struct BlogOptions<'a> {
    pub path: &'a str,
}

impl Blog<'static> {
    pub fn new(options: &'static BlogOptions) -> Self {
        Blog { options }
    }

    pub fn run(self, address: impl ToSocketAddrs) -> Result<(), io::Error> {
        let mut app = tide::App::with_state(self);
        app.at("/").get(|_| async move { "Hello world!" });
        app.run(address)?;
        Ok(())
    }
}
error
error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:20:27
   |
20 |     let blog = Blog::new(&BlogOptions { path: path.as_str() });
   |                -----------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-- temporary value is freed at the end of this statement
   |                |          |
   |                |          creates a temporary which is freed while still in use
   |                argument requires that borrow lasts for `'static`

error[E0597]: `path` does not live long enough
  --> src/main.rs:20:47
   |
20 |     let blog = Blog::new(&BlogOptions { path: path.as_str() });
   |                                               ^^^^---------
   |                                               |
   |                                               borrowed value does not live long enough
   |                                               argument requires that `path` is borrowed for `'static`
...
23 | }
   | - `path` dropped here while still borrowed

I'm still learning rust so feel free to provide better solutions on how I could had written blog.rs besides just static lifetimes.

Not sure why they require 'static (probably something to do with threading,) but you can generate static refs through leaking as long as you own the path/options:

pub struct BlogOptions {
    pub path: PathBuf,  // or String
}

pub struct Blog {
    options: BlogOptions,
}

impl Blog {
    pub fn new(options: BlogOptions) -> Self {
        Blog { options }
    }

    pub fn run(self, address: impl ToSocketAddrs) -> Result<(), io::Error> {
         let self = Box::leak(Box::new(self));
         let mut app = tide::App::with_state(self);
         app.at("/").get(|_| async move { "Hello world!" });
         Ok(())
    }
}

edit: Although it doesn't look like you really need the leaking; if you just have BlogOptions own the path and Blog own the options, that should be good enough to pass to with_state.

1 Like

It does work without leaks.

My first thought was to use &BlogOptions in fn new since coming from c that would be more performant compared to just BlogOptions. But since tide used static lifetime I started adding &static BlogOptions.

Why would one choose to pass by value here? Or is it something I'm missing here.

    pub fn new(options: BlogOptions) -> Self {
        Blog { options }
    }

I do understand that in this case new would take ownership so options would now be owned by Blog. But I was wondering if we could avoid copying. How would one write Blog in idiomatic rust?

In general (in my experience), choosing whether to pass by ref or value in Rust isn't based on performance (the optimizer is probably going to completely mess up any evaluation of that anyway,) it's based on the semantics you want. You're getting a String out of calling env::var, so you already own it; moving it into BlogOptions and then that into Blog is probably going to be elided so it's no overhead. Since you only need the path until Blog goes away, it makes sense to give Blog the ownership of that data.

(Additionally, assuming you're running on a 64-bit system, the size of a String (24 bytes) and the size of a &str (16 bytes) really aren't that different.)

1 Like

You should only use &'static str when all instances of the string are things written literally in your codebase. Either you should use String, or you shouldn't put lifetimes on your types and use 'static directly in the struct definition.