0

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

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

我现在想留下来'static,所以我的问题是......

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

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

我怎么能过&self.path一辈子'static

这是一个完整的例子:

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...

操场

4

3 回答 3

1

TL;DR 使用 aString而不是&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&'a str返回 a &'a str,而不是 a 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>&str不是&'static str. doit还需要能够创建重复String的s:

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

此 [...] 是配置,不会更改 [...] 在应用程序初始化期间从配置文件中读取的内容。

在这种情况下,创建一个单例,它会给你一个有效的静态值:

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

您可以将它与phimuemue 的答案结合起来得到 a &'static MyConfigStruct,然后它可以有 a fn foo(&'static self)that 可用。


如果这变得如此困难并且需要多次使用 mem-io,那么语言一定有问题。

你是部分正确的。使用今天的 Rust (1.30) 很难拥有最高性能的异步代码,因为 Rust 想要确保内存安全高于一切。这并不意味着代码性能不佳,只是还有一点空间可以做得更好。

老实说,在这里进行克隆不太可能成为性能瓶颈,但这很烦人。这就是语法asyncawait用武之地。这将允许期货更容易地以惯用的 Rust 方式使用引用。

因为运行时不需要一直运行 [...] 我理解错了吗?

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

也可以看看:

其他位

似乎这段代码中的每一次调用wait都是对期货的滥用。您可能希望重新阅读Tokio 文档以更好地了解您应该如何链接期货。如果有wait电话,通常是在一切结束时,甚至在使用 Tokio 时也很少见。

也可以看看:

于 2018-10-28T20:32:15.157 回答
0

我现在可以自己回答这个问题:

于 2018-10-29T00:18:07.147 回答
0

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

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

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

于 2018-10-28T17:45:07.800 回答