【问题标题】:Why will a move closure not capture a value with a generic type?为什么移动闭包不会捕获具有泛型类型的值?
【发布时间】:2019-10-13 08:42:09
【问题描述】:

我正在尝试创建一个 ScopeRunner 类型,该类型可以将方法调用存储到实现 Scope 特征的类型的方法,如下所示:

trait Scope {
    fn run(&self) -> String;
}

struct ScopeImpl;

impl Scope for ScopeImpl {
    fn run(&self) -> String {
        "Some string".to_string()
    }
}


struct ScopeRunner {
    runner: Box<dyn Fn() -> String>,
}

impl ScopeRunner {
    fn new<S: Scope>(scope: S) -> Self {
        ScopeRunner {
            runner: Box::new(move || scope.run())
        }
    }

    pub fn run(self) -> String {
        (self.runner)()
    }

}


fn main() {
    let scope = ScopeImpl {};
    let scope_runner = ScopeRunner::new(scope);

    dbg!(scope_runner.run());
}

我希望由于ScopeRunner::new 创建了一个移动闭包,这将导致范围被移动到闭包中。但是借用检查器却给了我这个错误:

error[E0310]: the parameter type `S` may not live long enough
  --> src/main.rs:21:30
   |
20 |     fn new<S: Scope>(scope: S) -> Self {
   |            -- help: consider adding an explicit lifetime bound `S: 'static`...
21 |         ScopeRunner {runner: Box::new(move || scope.run())}
   |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
note: ...so that the type `[closure@src/main.rs:21:39: 21:58 scope:S]` will meet its required lifetime bounds
  --> src/main.rs:21:30
   |
21 |         ScopeRunner {runner: Box::new(move || scope.run())}
   | 

当我将 ScopeRunner::new 替换为只需要 ScopeImpl 的非通用版本时,此代码确实有效。

fn new(scope: ScopeImpl) -> Self {
    ScopeRunner {
        runner: Box::new(move || scope.run())
    }
}

我不明白为什么这是不同的。在我看来,通用 Scope 的生命周期似乎与具体版本相同。

【问题讨论】:

标签: generics rust closures borrow-checker


【解决方案1】:

问题在于S 可以是任何具有Scope impl 的类型,其中包括所有类型的尚未存在的类型,这些类型带有对其他类型的引用。例如,您可以有这样的实现:

struct AnotherScope<'a> {
    reference: &'str,
}

impl Scope for ScopeImpl {
    fn run(&self) -> String {
        self.reference.to_string()
    }
}

Rust 很谨慎,并希望确保这适用于任何符合条件的S,包括它是否包含引用。

最简单的解决方法是按照错误说明的建议进行操作,并禁止 S 拥有任何非静态引用:

fn new<S: Scope + 'static>(scope: S) -> Self {
    ScopeRunner {
        runner: Box::new(move || scope.run())
    }
}

S'static 绑定实际上意味着S 可以包含对具有'static 生命周期的值的引用或根本不包含引用。

如果您想更灵活一点,可以将其扩展到比 ScopeRunner 本身寿命更长的引用:

struct ScopeRunner<'s> {
    runner: Box<dyn Fn() -> String + 's>,
}

impl<'s> ScopeRunner<'s> {
    fn new<S: Scope + 's>(scope: S) -> Self {
        ScopeRunner { 
            runner: Box::new(move || scope.run())
        }
    }

    pub fn run(self) -> String {
        (self.runner)()
    }
}

【讨论】:

  • 谢谢。似乎像编译器建议的那样用 'static 限制 S 在这里意味着与我预期的不同。这将成为我的 cmdr crate 的公共接口的一部分,我希望避免强迫用户指定生命周期,因为 Scope 将由他们实现,但我认为我不能在这里避免它。
  • @Mendelt 您的 API 用户大部分时间仍然不需要编写显式生命周期。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多