非常有趣的问题!我理解这里的问题.让我来解释一下.
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 Fn
trait 呢?据我所知,即使有《服务贸易总协定》,我们也不能.我认为不可能改变现有的Fn
特性,以向后兼容的方式使用GATs.因此,标准库很可能会保留不那么强大的特性.(旁注:如何以向后不兼容的方式发展标准库,以使用新的语言功能已经是I wondered about多次了;到目前为止,我还没有听说过这方面的任何真正计划;我希望Rust团队能想出一些办法……)
总结
您想要的并不是技术上不可能或不安全的(我们将其实现为一个简单的 struct ,并且可以正常工作).然而,不幸的是,现在不可能在Rust的类型系统中以闭包/Fn
特征的形式表达你想要的.这和streaming iterators正在处理的问题是一样的.
通过计划的GAT功能,可以在类型系统中表达所有这些.然而,标准库需要以某种方式跟上,以使您的确切代码成为可能.