如何获得 tokio::fs::File::open 的静态路径?

How do I get a static path for tokio::fs::File::open?

tokio::fs::File::open(path: T + 'static)path 参数需要 'static 生命周期。

这是有道理的,因为它是在程序执行期间在 运行时间线程中处理的。我认为如果你能度过自己的一生会更有意义,因为 运行 时间不需要一直 运行 所以你可以扔掉一些东西。我是不是理解错了?

我现在想待 'static,所以我的问题是……

我有一个 trait TraitN 和一些 struct StructX { path: String, } 有一个 fn new(path: &String) -> Box<TraitN>new 创建并设置 self.path = path.to_string();.

StructX 的一些实现 fn doit(&self) { ... } 中,我想调用 tokio::fs::File::open(&self.path).

如何在 'static 生命周期内通过 &self.path

这是一个完整的例子:

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

您可以限制 &self 的生命周期:

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

如果你这样做,你实际上可能更好地让 StructX 首先存储路径的 'static 借用(而不是字符串)。

TL;DR 使用 String 而不是 &str。当 async / await 语法稳定后,这可能会改变。


这是我根据您的原始问题制作的 MCVE:

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());
    }
}

要解决此问题,请克隆 String 并将其所有权授予函数:

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

您的示例代码存在很多问题:

fn start_runtime() -> Result<()> {
    let path: &str = get_path().wait().unwrap().as_str();
    tokio::run(doit(path.clone()));
    Ok(())
}
  1. 您不能引用 unwrap 的结果,因为没有任何东西拥有该值。你不能引用这种临时文件。

  2. 克隆 &'a str returns &'a str,而不是 String.

  3. 对值调用 wait 没有意义,因为它会阻塞线程。 运行 反应堆循环中的所有内容。

这个函数应该看起来像

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

那么你所有的代码都应该切换到 impl Into<String> 而不是 &'static str&strdoit 还需要能够创建重复的 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.

在这种情况下,create a singleton 将为您提供有效静态值:

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")
    };
}

然后将所有值更改为 &'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
        })
    }
}

并引用单例:

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

您可以将其与 结合使用以获得 &'static MyConfigStruct,然后可以使用 fn foo(&'static self)


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

你说对了一部分。今天的 Rust (1.30) 很难拥有最高性能的异步代码,因为 Rust 想要确保内存安全高于一切。这并不意味着代码性能不佳,只是还有改进的余地。

老实说,在这里克隆不太可能成为性能瓶颈,但很烦人。这就是 async and await syntax 的用武之地。这将使 futures 能够更轻松地以惯用的 Rust 方式使用引用。

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

但是,asyncawait 仍然可能无法帮助您,因为默认情况下 Tokio 将 运行 您的未来放在不同的线程上。这是它需要 'static 绑定的主要原因之一。这可以防止 Tokio 线程引用超出范围的本地堆栈,从而引入内存不安全。然而,这并不是 Tokio 独有的问题。

另请参阅:

  • How can I pass a reference to a stack variable to a thread?

其他位

似乎 在此代码中对 wait 的每个 调用都是对期货的滥用。您可能希望重新阅读 the Tokio docs 以更好地理解您应该如何链接期货。如果有一个 wait 调用,它通常在所有内容的末尾,即使在使用 Tokio 时也很少见。

另请参阅:

我现在可以自己回答了: