0

The tokio::fs::File::open(path: T + 'static) requires a 'static lifetime on its path parameter.

This makes sense because it is handled in runtime threads during the program's execution. I think it would make more sense if you could pass your own lifetimes, because the runtime does not need to run the whole time and so you could throw away some stuff. Do I understand something wrong?

I'd like to stay for 'static at the moment and so my problem is this...

I have a trait TraitN and some struct StructX { path: String, } with a fn new(path: &String) -> Box<TraitN>. The new creates and sets self.path = path.to_string();.

In some impl fn doit(&self) { ... } for StructX, I'd like to call tokio::fs::File::open(&self.path).

How can I pass &self.path with a 'static lifetime?

This is a complete example:

extern crate futures;
extern crate tokio;
#[macro_use]
extern crate error_chain;

use futures::future;
use futures::future::{loop_fn, ok, Future, Loop};
use futures::Stream;
use std::io::BufReader;
use tokio::{fs, io};

mod error {
    error_chain!{}
}

use error::*;

type FutureResult<T> = future::FutureResult<T, Error>;

trait HandlerTrait {
    fn new(path: &str) -> Box<HandlerTrait>
    where
        Self: Sized;
    fn get_all(&self) -> FutureResult<Vec<String>>;
}

#[derive(Debug)]
pub struct Handler {
    path: String,
}

impl HandlerTrait for Handler {
    fn new(path: &str) -> Box<HandlerTrait> {
        Box::new(Handler {
            path: path.to_string(),
        })
    }

    fn get_all(&self) -> FutureResult<Vec<String>> {
        let file = fs::File::open(self.path.clone())
            .and_then(|file: fs::File| ok(file))
            .wait()
            .unwrap();
        let lines = io::lines(BufReader::new(file));
        ok(lines
            .filter(|line| line.len() > 80)
            .map(|all| all[0..80].to_string())
            .collect()
            .wait()
            .unwrap())
    }
}

fn get_handler(path: &str) -> Option<Box<HandlerTrait>> {
    Some(Handler::new(path))
}

fn get_path() -> FutureResult<String> {
    ok("./somepath/file".to_string())
}

fn start_runtime() -> Result<()> {
    let path: &str = get_path().wait().unwrap().as_str();
    tokio::run(doit(path.clone()));
    Ok(())
}

fn doit(path: &'static str) -> impl Future<Item = (), Error = ()> + 'static {
    let n = 0;
    loop_fn(n, move |_nr| {
        let lh = get_handler(path).unwrap();
        lh.get_all()
            .or_else(|_| Err(()))
            .and_then(|_all| ok(Loop::Break(())))
    })
}

#[test]
fn test() {
    start_runtime().unwrap();
    assert!(true);
}
error[E0597]: borrowed value does not live long enough
  --> src/lib.rs:63:22
   |
63 |     let path: &str = get_path().wait().unwrap().as_str();
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^         - temporary value only lives until here
   |                      |
   |                      temporary value does not live long enough
   |
   = note: borrowed value must be valid for the static lifetime...

playground

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Markus
  • 512
  • 1
  • 4
  • 21
  • Please review how to create a [MCVE] and then [edit] your question to include it. We cannot tell what crates, types, traits, fields, etc. are present in the code. Try to produce something that reproduces your error on the [Rust Playground](https://play.rust-lang.org) or you can reproduce it in a brand new Cargo project. There are [Rust-specific MCVE tips](//stackoverflow.com/tags/rust/info) as well. – Shepmaster Oct 28 '18 at 20:25
  • [Why is it discouraged to accept a reference to a String (&String), Vec (&Vec) or Box (&Box) as a function argument?](https://stackoverflow.com/q/40006219/155423). – Shepmaster Oct 28 '18 at 20:29

3 Answers3

1

TL;DR Use a String instead of a &str. This might change when async / await syntax is stabilized.


Here's the MCVE I made of your original question:

extern crate tokio; // 0.1.11

trait TraitN {}

struct StructX {
    path: String,
}

impl TraitN for StructX {}

fn new(path: &str) -> Box<TraitN> {
    Box::new(StructX {
        path: path.to_string(),
    })
}

impl StructX {
    fn doit(&self) {
        tokio::fs::File::open(self.path.clone());
    }
}

To solve this, clone the String and give ownership of it to the function:

impl StructX {
    fn doit(&self) {
        tokio::fs::File::open(self.path.clone());
    }
}

With your example code, there are numerous problems:

fn start_runtime() -> Result<()> {
    let path: &str = get_path().wait().unwrap().as_str();
    tokio::run(doit(path.clone()));
    Ok(())
}
  1. You cannot take a reference to the result of unwrap because nothing will own that value. You cannot have a reference to this kind of temporary.

  2. Cloning a &'a str returns a &'a str, not a String.

  3. It doesn't make sense to call wait on the value because that blocks the thread. Run everything in the reactor loop.

This function should look like

fn start_runtime() -> Result<()> {
    tokio::run({
        get_path()
            .map_err(|e| panic!("{}", e))
            .and_then(|path| doit(path))
    });
    Ok(())
}

Then all of your code should switch to impl Into<String> instead of &str of &'static str. doit also needs to be able to create duplicate Strings:

fn doit(path: impl Into<String> + Clone) -> impl Future<Item = (), Error = ()> + 'static {
    let n = 0;
    let path = path.into();
    loop_fn(n, move |_nr| {
        let lh = get_handler(path.clone()).unwrap();
        lh.get_all()
            .or_else(|_| Err(()))
            .and_then(|_all| ok(Loop::Break(())))
    })
}

this [...] is config which doesn't change [...] read from a configfile during app init.

In that case, create a singleton which will give you an effectively-static value:

extern crate lazy_static; // 1.1.0

use lazy_static::lazy_static;

lazy_static! {
    static ref PATH: String = {
        // Should be read from a file.
        String::from("/the/path/to/the/thing")
    };
}

Then change all of the values to &'static str:

#[derive(Debug)]
pub struct Handler {
    path: &'static str,
}

impl HandlerTrait for Handler {
    fn new(path: &'static str) -> Box<HandlerTrait> {
        Box::new(Handler {
            path
        })
    }
}

And take a reference to the singleton:

fn start_runtime() -> Result<()> {
    tokio::run(doit(&PATH));
    Ok(())
}

You can couple this with phimuemue's answer to get a &'static MyConfigStruct, which could then have a fn foo(&'static self) that is available.


There must be something wrong with a language if this becomes so difficult and needs mem-io multiple times.

You are partially correct. It's difficult to have maximally performant async code with today's Rust (1.30) because Rust wants to ensure memory safety above all else. This doesn't mean that the code is unperformant, just that there's a bit of room to do better.

Honestly, making clones here is unlikely to be a performance bottleneck, but it is annoying. That's where async and await syntax comes in. This will allow futures to more easily make use of references in an idiomatic Rust manner.

because the runtime does not need to run the whole time [...] Do I understand something wrong?

However, async and await still might not help you, as by default Tokio will run your future on a different thread. That's one of the primary reasons it requires a 'static bound. This prevents a Tokio thread from having a reference to a stack local that goes out of scope, introducing memory unsafety. This isn't a unique problem to Tokio, however.

See also:

Other bits

It appears that every single call to wait in this code is a misuse of futures. You may wish to re-read the Tokio docs to better understand understand how you are supposed to chain futures. If there's a wait call, it's usually at the end of everything, and even that is rare when using Tokio.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Mmh. Do I have not tried the cloning, because the compiler was talking about lifetimes? I'm not sure. I'll see. – Markus Oct 28 '18 at 20:46
  • I'm sorry, but the clone doesn't work. open() wants to have a ['static](https://tokio-rs.github.io/tokio/tokio_fs/file/struct.File.html#method.open) - Perhaps you can show me, how you can implement fn make_static_str<'a>(s: &'a str) -> &'static str; – Markus Oct 28 '18 at 21:18
  • @Markus *`fn make_static_str<'a>(s: &'a str) -> &'static str`.* please search for existing questions: [How to convert a String into a &'static str](https://stackoverflow.com/q/23975391/155423). However, [as I commented](https://stackoverflow.com/questions/53033807/how-do-i-get-a-static-path-for-tokiofsfileopen/53035816?noredirect=1#comment92971769_53034427), doing so is usually a **very bad idea**. – Shepmaster Oct 28 '18 at 21:24
  • Ok, I understand this, but I don't know how I should create a future for [tokio::run()](https://docs.rs/tokio/0.1.12/tokio/runtime/fn.run.html) and how the clone should work, when there are everywhere 'static requirements. The clone of &str is a &str and not a &'static str?! – Markus Oct 28 '18 at 21:32
  • I've updated the question with a link to rust playground. – Markus Oct 28 '18 at 22:45
  • Ok, thanks. But it doesn't change anything that I'm feeling bad about using stacko. I don't get the answers to my questions. At the end this stupid path is static const (it is config which doesn't change), but is read from a configfile during app init. It is stored in memory the whole lifecycle of the app. I don't see any need in all these clonings. A mutable reference should be enough for a million of threads to read this memory. And all without cloning, moving, borrowing, locking. There must be something wrong with a language if this becomes so difficult and needs mem-io multiple times. – Markus Oct 29 '18 at 15:26
  • *At the end this [...] is config which doesn't change [...] read from a configfile during app init* — this is important information to include. If that's the case, then [make it a singleton](https://stackoverflow.com/q/27791532/155423), which will make the string itself effectively static. I'll add an example. – Shepmaster Oct 29 '18 at 15:29
  • I use lazy_static already, but together with the config crate and a RWLock. I have to see, if it is possible to get something static out. – Markus Oct 30 '18 at 09:43
  • @Markus again, more detail that you've left out. In that case, no, you cannot get a static reference from it because at **any** point in the future the `RwLock` might be locked and the inner value destroyed. Rust will not allow you to introduce memory unsafety. – Shepmaster Oct 30 '18 at 12:55
0

You can restrict the lifetime of &self:

impl StructX {
    fn doit(&'static self) {
        // here, we know that self and its members are 'static
    }
}

If you do this, you may actually be better off to have StructX store the 'static borrow of the path in the first place (instead of a string).

phimuemue
  • 34,669
  • 9
  • 84
  • 115
  • I would prefer the later, but I don't know how this can be done, because I need some place to make a dynamic string to a static one. Maybe the StructX is the place?! Don't know if this is the rust way or there is a better possibility?! – Markus Oct 28 '18 at 18:20
  • Not sure... Is it possible to get a static return type, if I move something non-static in? – Markus Oct 28 '18 at 18:56
  • 1
    While this is technically true, it's mostly useless as there's very few ways to create a structure that meet the requirements without doing terrible things to the rest of the program, like leaking memory. – Shepmaster Oct 28 '18 at 20:29
0

I can answer this now by myself:

Markus
  • 512
  • 1
  • 4
  • 21