我遇到了一生无法理解的怪癖。我不确定如何表达这个问题,所以很难通过谷歌搜索找到解决方案。
我有一个创建特征实例的函数,并且返回的 impl 具有生命周期限制
'a
。该函数采用两个参数 (not_borrowed: T, borrowed: &'a str)
,其中 not_borrowed
仅预期在函数主体中可用,而 borrowed
的生命周期为 'a
,这意味着它可以存储在返回的特征中。
在这个例子中,我只是为 () 实现了一个虚拟特征。
然后当我从外部函数调用函数时,编译器给出错误:
error[E0597]: `val` does not live long enough
--> src/main.rs:9:28
|
8 | fn outer<'a>(val: String) -> impl ReturnedTrait + 'a {
| -- lifetime `'a` defined here
9 | let ret = create_trait(&val, "");
| -------------^^^^-----
| | |
| | borrowed value does not live long enough
| argument requires that `val` is borrowed for `'a`
...
13 | }
| - `val` dropped here while still borrowed
我不希望发生这种情况,因为我从来没有要求
T
为'a
而活。
我注意到我可以通过执行下面注释行中的操作来编译代码。我不确定为什么编译器会突然改变主意并认为它没问题只是因为我把值放在一个盒子里?
trait ReturnedTrait {}
impl<T: ReturnedTrait + ?Sized> ReturnedTrait for Box<T> {}
impl ReturnedTrait for () {}
fn create_trait<'a, T>(not_borrowed: T, borrowed: &'a str) -> impl ReturnedTrait + 'a {}
fn outer<'a>(val: String) -> impl ReturnedTrait + 'a {
let ret = create_trait(&val, "");
// Uncomment this line and the code compiles
// let ret: Box<dyn ReturnedTrait + 'a> = Box::new(ret);
ret
}
这是您的代码的简化版本和一些变体,这应该有助于解释发生了什么(Rust Playground 链接):
#![feature(type_alias_impl_trait)]
#![feature(unsize)]
use core::marker::Unsize;
use core::fmt::Debug;
pub fn returns_unrelated_impl<T>() -> impl Debug + 'static { () }
returns_unrelated_impl
是create_trait
的简化版本。事实证明,函数有任何参数根本无关紧要 - 重要的是泛型类型参数(这里是论证立场)。T
第一次测试没有impl Trait
,失败的原因与您的示例相同(以及与第一条评论中链接的帖子相同的原因)。 Rust 要求返回位置中的an
pub fn test_1<'a>() -> impl Debug {
returns_unrelated_impl::<&'a ()>()
}
相对于同一函数的通用参数应该是可变的,即使这实际上并不是使类型系统健全所必需的。 (在链接的帖子中有一些潜在的动机。)
但是,因为这种变异要求不是健全性所必需的,所以编译器实际上只在类型检查器需要检查变异的情况下检查它。这是使用
Box
的上述代码的等价物,其中 impl Trait
的返回值需要是一个
Box<dyn debug>
:returns_unrelated_impl
此测试失败的原因与 impl Unsize<dyn Debug> + 'static
相同:编译器尝试检查
pub fn test_2a<'a>() -> impl Debug {
type ImplUnsizeStatic = impl Unsize<dyn Debug> + 'static;
let rv1: ImplUnsizeStatic = returns_unrelated_impl::<&'a ()>();
let rv2: Box<dyn Debug> = Box::new(rv1);
rv2
}
上的
test_1
变体是否是未提及 impl Debug + 'static
的 T
,并发现方差不匹配(即使对impl Unsize<dyn Debug> + 'static
方差的要求完全是人为的)。稍微更改代码会导致它现在可以编译:T
T
检查
pub fn test_2b<'a>() -> impl Debug {
fn verify_impl_unsize_static<T: Unsize<dyn Debug> + 'static>(x: &T) {}
let rv1 = returns_unrelated_impl::<&'a ()>();
verify_impl_unsize_static(&rv1);
let rv2: Box<dyn Debug> = Box::new(rv1);
rv2
}
是否是一个
test_2a
,而 rv1
检查 impl Unsize<dyn Debug> + 'static
是否实现 test_2b
。这两个语句看起来几乎相同,但编译器对它们的解释不同;在 rv1
和 Unsize<dyn Debug> + 'static
中,我试图将一个 test_1
类型强制转换为另一个 test_2a
类型,编译器注意到 impl Trait
不再被捕获,而 impl Trait
只是检查是否 T
type 有一个合适的类型来实现 test_2b
和 impl Trait
生命周期,它确实如此。拳击解决原始问题的原因与 Unsize<dyn Debug>
起作用的原因相同:将 'static
强制转换为
test_2b
的要求是 ,即
Box<T>
实现
Box<dyn Debug>
,因此强制转换为
T
正在检查与
Unsize<dyn Debug> + 'static
所做的完全相同的事情——它正在检查 Box<dyn Debug>
的返回值是否实现了一个特征,而不是它
是否可以被强制转换为给定的
verify_impl_unsize_static
类型,以及人工捕获泛型类型参数的要求似乎只在后一种情况下被检查。
就其价值而言,我认为这种行为非常令人惊讶:从程序员的角度来看,
returns_unrelated_impl
和 impl
的行为不同并没有多大意义,这让我怀疑
的预期要求test_2a
在返回位置键入以捕获函数的通用参数仅部分/不完全实现。尽管上面的示例可能解释了当前的行为,但如果当前的行为是语言作者实际希望编译器执行的操作,我会感到非常惊讶。我相信发生的事情是当你有这样的功能时:
你用带有生命周期的
impl Trait
调用它,它会自动将生命周期添加到
fn f<T>(a: T) -> impl Trait
。您可以通过添加
T
绑定来解决此问题:impl Trait
在某种程度上这是有道理的:该函数可以返回一段'static
。不过很难注意到。
你的函数,当用
where T: 'static
调用时,变成:
a