考虑到以下准则:

fn foo<'a, T: 'a>(t: T) -> Box<Fn() -> &'a T + 'a> {
    Box::new(move || &t)
}

我期待的是:

  • T型的生命周期 为'a年.
  • t的生命周期 和T的生命周期 一样长.
  • t移动到闭包,因此闭包的生命周期 与t一样长
  • 闭包返回对t的引用,该引用被移动到闭包中.所以只要闭包存在,引用就有效.
  • 代码编译时,没有生命周期问题.

实际发生的情况:

  • 代码不编译:
error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
 --> src/lib.rs:2:22
  |
2 |     Box::new(move || &t)
  |                      ^^
  |
note: first, the lifetime cannot outlive the lifetime  as defined on the body at 2:14...
 --> src/lib.rs:2:14
  |
2 |     Box::new(move || &t)
  |              ^^^^^^^^^^
note: ...so that closure can access `t`
 --> src/lib.rs:2:22
  |
2 |     Box::new(move || &t)
  |                      ^^
note: but, the lifetime must be valid for the lifetime 'a as defined on the function body at 1:8...
 --> src/lib.rs:1:8
  |
1 | fn foo<'a, T: 'a>(t: T) -> Box<Fn() -> &'a T + 'a> {
  |        ^^
  = note: ...so that the expression is assignable:
          expected std::boxed::Box<(dyn std::ops::Fn() -> &'a T + 'a)>
             found std::boxed::Box<dyn std::ops::Fn() -> &T>

我不理解这场冲突.我该怎么修?

推荐答案

非常有趣的问题!我理解这里的问题.让我来解释一下.

tl;dr: closures cannot return references to values captured by moving, because that would be a reference to 101. Such a reference cannot be returned because the 102 traits don't allow us to express that.这与streaming iterator problem基本相同,可以通过GATs(通用关联类型)进行修复.


手动实现

正如你可能知道的,当你写一个闭包时,编译器会为Fn个适当的特征生成一个 struct 和impl个块,所以闭包基本上是语法糖.让我们试着避免所有的糖分,手动创建你的类型.

您想要的是一个类型,它可以包含owns个其他类型,并且可以返回对该拥有类型的引用.你需要一个函数,它返回一个上述类型的装箱实例.

struct Baz<T>(T);

impl<T> Baz<T> {
    fn call(&self) -> &T {
        &self.0
    }
}

fn make_baz<T>(t: T) -> Box<Baz<T>> {
    Box::new(Baz(t))
}

这相当于你的盒装封口.让我们试着使用它:

let outside = {
    let s = "hi".to_string();
    let baz = make_baz(s);
    println!("{}", baz.call()); // works

    baz
};

println!("{}", outside.call()); // works too

这个很好用.字符串s被移动到Baz类型中,Baz实例被移动到Box中.s现在归baz所有,然后归outside所有.

当我们添加一个字符时,它会变得更有趣:

let outside = {
    let s = "hi".to_string();
    let baz = make_baz(&s);  // <-- NOW BORROWED!
    println!("{}", baz.call()); // works

    baz
};

println!("{}", outside.call()); // doesn't work!

现在我们不能让baz的生存期大于s的生存期,因为baz包含对s的引用,这将是一个悬空的引用,s的引用将在baz之前超出范围.

我想用这段代码说明的一点是:我们不需要在type Baz上注释任何生命周期来确保安全;Rust自己发现了这一点,并强制执行baz的生命周期 不超过s.这在下文中将非常重要.

为它写一个特征

到目前为止,我们只讨论了基本知识.让我们试着写一个像Fn这样的trait 来接近你最初的问题:

trait MyFn {
    type Output;
    fn call(&self) -> Self::Output;
}

在我们的trait中,没有函数参数,但在其他方面,它与the real Fn trait完全相同.

让我们实施它吧!

impl<T> MyFn for Baz<T> {
    type Output = ???;
    fn call(&self) -> Self::Output {
        &self.0
    }
}

现在我们有一个问题:我们写什么而不是????天真的人会写&T...但我们需要一个生命周期参数来进行引用.我们从哪里弄到的?返回值的生命周期是多少?

让我们判断一下之前实现的功能:

impl<T> Baz<T> {
    fn call(&self) -> &T {
        &self.0
    }
}

所以这里我们也使用&T,不带生命周期 参数.但这只适用于终身省略.基本上,编译器会填充空格,因此fn call(&self) -> &T相当于:

fn call<'s>(&'s self) -> &'s T

啊哈,所以返回引用的生存期是self年!(更有经验的铁 rust 用户可能已经有了这种感觉……).

(作为旁注:为什么返回的引用不依赖于T本身的生存期?如果T引用了非'static的东西,那么这必须被考虑,对吗?是的,但它已经被考虑了!记住,Baz<T>的实例不能比T可能引用的东西活得更长.所以self的生存期已经比也许是T岁.因此,我们只需要专注于人生)

但我们如何在trait 暗示中表达这一点呢?结果是:we can't(尚未).在streaming iterators的上下文中经常提到这个问题,也就是说,迭代器返回的项的生存期绑定到self个生存期.不幸的是,在今天的铁 rust 中,这是不可能实现的;打字系统不够强大.

future 呢?

幸运的是,有一个RFC "Generic Associated Types"在不久前被合并了.该RFC扩展了Rust 类型系统,允许相关类型的性状具有通用性(超过其他类型和生命周期 ).

让我们来看看如何让你的例子(有点)与GATs一起工作(根据RFC;这些东西还不起作用)☹). 首先,我们必须改变trait 定义:

trait MyFn {
    type Output<'a>;   // <-- we added <'a> to make it generic
    fn call(&self) -> Self::Output;
}

代码中的函数签名没有更改,但请注意,终身省略开始生效!上述fn call(&self) -> Self::Output相当于:

fn call<'s>(&'s self) -> Self::Output<'s>

因此,关联类型的生存期绑定到self生存期.就像我们想要的!impl看起来像这样:

impl<T> MyFn for Baz<T> {
    type Output<'a> = &'a T;
    fn call(&self) -> Self::Output {
        &self.0
    }
}

要退回盒装MyFn,我们需要写下以下内容(根据this section of the RFC:

fn make_baz<T>(t: T) -> Box<for<'a> MyFn<Output<'a> = &'a T>> {
    Box::new(Baz(t))
}

如果我们想使用real Fntrait 呢?据我所知,即使有《服务贸易总协定》,我们也不能.我认为不可能改变现有的Fn特性,以向后兼容的方式使用GATs.因此,标准库很可能会保留不那么强大的特性.(旁注:如何以向后不兼容的方式发展标准库,以使用新的语言功能已经是I wondered about多次了;到目前为止,我还没有听说过这方面的任何真正计划;我希望Rust团队能想出一些办法……)


总结

您想要的并不是技术上不可能或不安全的(我们将其实现为一个简单的 struct ,并且可以正常工作).然而,不幸的是,现在不可能在Rust的类型系统中以闭包/Fn特征的形式表达你想要的.这和streaming iterators正在处理的问题是一样的.

通过计划的GAT功能,可以在类型系统中表达所有这些.然而,标准库需要以某种方式跟上,以使您的确切代码成为可能.

Rust相关问答推荐

如何从使用mockall模拟的方法中返回self?

如何从接收&;self的方法克隆RC

为什么BitVec缺少Serialize trait?

如何格式化传入Rust中mysql crate的Pool::new的字符串

Rust 的多态现象.AsRef与Derf

为什么`AlternateScreen`在读取输入键时需要按Enter键?

更合理的方法来设计样条线函数器?

Cargo.toml:如何有条件地启用依赖项功能?

当我编译 Rust 代码时,我是否缺少 AVX512 的目标功能?

如何重命名 clap_derive 中的子命令占位符?

如何强制匹配的返回类型为()?

为什么切片时需要参考?

部署Rust发布二进制文件的先决条件

如何将 Rust 中的树状 struct 展平为 Vec<&mut ...>?

在 Rust 中,为什么整数溢出有时会导致编译错误或运行时错误?

Rust 中函数的类型同义词

在 RefCell 上borrow

在 Rust 中退出进程

Rust 为什么 (u32, u32) 的枚举变体的大小小于 (u64)?

C++ 中的 CRTP 是一种表达其他语言中特征和/或 ADT 的方法吗?