生命周期省略
在编译器可以推断出一些合理的默认选择规则时,允许省略生命周期。
也可以用同样的方式推断占位符生命周期 '_
。
对于路径上的生命周期,使用 '_
很好的选择。
Trait 对象生命周期遵循的规则有所不同,将在下面讨论。
函数中的生命周期省略
为了让常见的模式更加人性化,生命周期参数可以在函数、函数指针和闭包 Trait 签名中被 省略 。 省略无法推断的生命周期参数将是错误。
- 在参数中每个被省略的生命周期都会成为单独的。
- 如果在参数中只有一个生命周期 (不论是否被省略) ,那么该生命周期将分配到 所有 被省略的输出生命周期。
在方法签名中还有一个规则:
- 如果接收者的类型是
&Self
或&mut Self
,那么指向Self
的这个引用的生命周期会被分配到所有省略的输出生命周期参数。
例子:
#![allow(unused)] fn main() { trait T {} trait ToCStr {} struct Thing<'a> {f: &'a i32} struct Command; trait Example { fn print1(s: &str); // 省略生命周期 fn print2(s: &'_ str); // 同样省略 fn print3<'a>(s: &'a str); // 展开 fn debug1(lvl: usize, s: &str); // 省略生命周期 fn debug2<'a>(lvl: usize, s: &'a str); // 展开 fn substr1(s: &str, until: usize) -> &str; // 省略生命周期 fn substr2<'a>(s: &'a str, until: usize) -> &'a str; // 展开 fn get_mut1(&mut self) -> &mut dyn T; // 省略生命周期 fn get_mut2<'a>(&'a mut self) -> &'a mut dyn T; // 展开 fn args1<T: ToCStr>(&mut self, args: &[T]) -> &mut Command; // 省略生命周期 fn args2<'a, 'b, T: ToCStr>(&'a mut self, args: &'b [T]) -> &'a mut Command; // 展开 fn new1(buf: &mut [u8]) -> Thing<'_>; // 省略生命周期 - 推荐 fn new2(buf: &mut [u8]) -> Thing; // 省略生命周期 fn new3<'a>(buf: &'a mut [u8]) -> Thing<'a>; // 展开 } type FunPtr1 = fn(&str) -> &str; // 省略生命周期 type FunPtr2 = for<'a> fn(&'a str) -> &'a str; // 展开 type FunTrait1 = dyn Fn(&str) -> &str; // 省略生命周期 type FunTrait2 = dyn for<'a> Fn(&'a str) -> &'a str; // 展开 }
#![allow(unused)] fn main() { // 以下示例展示了无法省略生命周期参数的情况。 trait Example { // 无法推断,因为没有参数可推断。 fn get_str() -> &str; // ILLEGAL // 无法推断,不清楚是从第一个还是第二个参数中借用。 fn frob(s: &str, t: &str) -> &str; // ILLEGAL } }
默认trait对象生命周期
由 trait 对象 持有的引用的假定的生命周期称为其 默认对象生命周期约束 。 这些在 RFC 599 中定义并在 RFC 1156 中进行了修改。
这些默认对象生命周期约束在完全省略生命周期约束时使用,而不是使用上述省略生命周期参数的规则。
如果将 '_
用作生命周期约束,则该约束遵循通常的省略规则。
如果 trait 对象用作泛型类型的类型参数,则首先使用包含类型来尝试推断约束。
- 如果包含类型存在唯一的约束,则使用该约束作为默认值。
- 如果来自包含类型的约束超过一个,则必须指定显式约束。
如果上述规则都不适用,则使用 trait 的约束:
- 如果 trait 使用单个生命周期约束定义,则使用该约束。
- 如果使用
'static
作为任意生命周期约束,则使用'static
。 - 如果 trait 没有生命周期约束,则在表达式中推断生命周期,并在表达式外部使用
'static
。
#![allow(unused)] fn main() { // 对于下面的 trait... trait Foo { } // 这两者是相同的,因为 Box<T> 没有 T 上的生命周期约束 type T1 = Box<dyn Foo>; type T2 = Box<dyn Foo + 'static>; // ... 这两者也相同: impl dyn Foo {} impl dyn Foo + 'static {} // ... 这两者也相同,因为 &'a T 要求 T: 'a type T3<'a> = &'a dyn Foo; type T4<'a> = &'a (dyn Foo + 'a); // std::cell::Ref<'a, T> 同样要求 T: 'a ,所以这两者相同 type T5<'a> = std::cell::Ref<'a, dyn Foo>; type T6<'a> = std::cell::Ref<'a, dyn Foo + 'a>; }
#![allow(unused)] fn main() { // 这是一个错误的示例。 trait Foo { } struct TwoBounds<'a, 'b, T: ?Sized + 'a + 'b> { f1: &'a i32, f2: &'b i32, f3: T, } type T7<'a, 'b> = TwoBounds<'a, 'b, dyn Foo>; // ^^^^^^^ // 错误:无法从上下文中推断出对象类型的生命周期约束 }
请注意,最内层的对象设置了生命周期约束,因此 &'a Box<dyn Foo>
仍然等同于 &'a Box<dyn Foo + 'static>
。
#![allow(unused)] fn main() { // 对于下面的 trait ... trait Bar<'a>: 'a { } // ...这两个是相同的: type T1<'a> = Box<dyn Bar<'a>>; type T2<'a> = Box<dyn Bar<'a> + 'a>; // ...这两个也是相同的: impl<'a> dyn Bar<'a> {} impl<'a> dyn Bar<'a> + 'a {} }
'static
生命周期省略
除非显式指定生命周期,否则引用类型的常量和静态声明都有 隐含的 'static
生命周期。
因此,上述涉及 'static
的常量声明可以不带生命周期。
#![allow(unused)] fn main() { // STRING: &'static str const STRING: &str = "bitstring"; struct BitsNStrings<'a> { mybits: [u32; 2], mystring: &'a str, } // BITS_N_STRINGS: BitsNStrings<'static> const BITS_N_STRINGS: BitsNStrings<'_> = BitsNStrings { mybits: [1, 2], mystring: STRING, }; }
请注意,如果 static
或 const
条目包含函数或闭包引用,这些引用本身又包含引用,编译器将首先尝试标准省略规则。
如果它无法通过通常的规则解析生命周期,则会出现错误。以下是示例:
#![allow(unused)] fn main() { struct Foo; struct Bar; struct Baz; fn somefunc(a: &Foo, b: &Bar, c: &Baz) -> usize {42} // 解析为 `fn<'a>(&'a str) -> &'a str`. const RESOLVED_SINGLE: fn(&str) -> &str = |x| x; // 解析为 `Fn<'a, 'b, 'c>(&'a Foo, &'b Bar, &'c Baz) -> usize`. const RESOLVED_MULTIPLE: &dyn Fn(&Foo, &Bar, &Baz) -> usize = &somefunc; }
#![allow(unused)] fn main() { struct Foo; struct Bar; struct Baz; fn somefunc<'a,'b>(a: &'a Foo, b: &'b Bar) -> &'a Baz {unimplemented!()} // 缺少信息来将返回的引用生命周期绑定到参数的生命周期, // 所以会出现错误。 const RESOLVED_STATIC: &dyn Fn(&Foo, &Bar) -> &Baz = &somefunc; // ^ // 此函数的返回类型包含了一个借用的值,但是函数签名并未表明它是从参数 1 还是参数 2 借用的。 }