返回类型的impl Trait
语法似乎会导致编译器错误地认为,在某些情况下,输入参数的生存期必须与输出匹配。考虑功能
fn take_by_trait<T: InTrait>(_: T) -> impl OutTrait {}
如果输入类型包含生存期,则编译器会在输出超过生存期时发出抱怨,即使它们是完全独立的。如果输入类型不是泛型,或者输出是Box<dyn OutTrait>
,则不会发生这种情况。
完整代码:
trait InTrait {}
struct InStruct<'a> {
_x: &'a bool, // Just a field with some reference
}
impl<'a> InTrait for InStruct<'a> {}
trait OutTrait {}
impl OutTrait for () {}
fn take_by_type(_: InStruct) -> impl OutTrait {}
fn take_by_trait<T: InTrait>(_: T) -> impl OutTrait {}
fn take_by_trait_output_dyn<T: InTrait>(_: T) -> Box<dyn OutTrait> {
Box::new(())
}
fn main() {
let _ = {
let x = true;
take_by_trait(InStruct{ _x: &x }) // DOES NOT WORK
// take_by_type(InStruct { _x: &x }) // WORKS
// take_by_trait_output_dyn(InStruct { _x: &x }) // WORKS
};
}
这里是否有一些被忽略的生命周期,我有资格让它发挥作用,或者我需要进行堆分配?
impl Trait
语义意味着函数返回实现Trait
的某个类型,但调用方不能对该类型或使用寿命做出任何假设。
众所周知,take_by_trait
函数可以在许多不同的模块中使用,或者可能在其他板条箱中使用。现在,您的实现在所有用例中都可以正常工作。它可以像一样重写
fn take_by_trait<T: InTrait>(_: T) -> () {}
这是一个非常好的功能,工作起来也很好。但是,在某个时刻,您可能需要为OutTrait
添加另一个实现,并稍微更改take_by_trait
函数。
trait OutTrait { fn use_me(&self) {} }
impl<T: InTrait> OutTrait for T {}
fn take_by_trait<T: InTrait>(v: T) -> impl OutTrait {v}
如果我们扩展通用参数和impl
的定义,我们得到的代码是:
fn take_by_trait<'a>(v: InStruct<'a>) -> InStruct<'a> {v}
fn main() {
let v = {
let x = true;
take_by_trait(InStruct{ _x: &x })
};
v.use_me();
}
这显然不起作用,因为x
在println!
尝试访问其值之前就被丢弃了。因此,通过为OutTrait
添加一个新的实现,您破坏了使用该函数的代码,可能是在机箱中的某个位置,这取决于您的。这就是为什么编译器不愿意允许您定义这样的东西。
因此,impl OutTrait
的问题再次出现,只是编译器不能对返回的类型及其生存期做出任何假设,所以它使用了最大可能的边界,这会产生您看到的借用检查器错误。
编辑:我对代码做了一些修改,这样函数的签名就不会改变,而且代码实际上编译并产生了相同的终身错误:操场
impl Trait
在返回位置隐式捕获泛型参数中出现的任何生存期。这不是在普通Rust中可以表达的,只能用impl Trait
来表达。
据我所知,在马厩里没有办法避免这种情况。在夜间,您可以使用type_alias_impl_trait
:
#![feature(type_alias_impl_trait)]
type Ret = impl OutTrait;
fn take_by_trait<T: InTrait>(v: T) -> Ret {}
请参阅问题当impl Trait返回值捕获非静态参数(#82171(,impl Trait捕获类型参数的生存期(#79415(,假阳性时的编译器错误;借用时临时丢弃";涉及返回位置impl Trait(#98997(,如果从泛型函数(#76882(返回,则impl Trait+的static不是静态的。