练习题来自https://practice-zh.course.rs/lifetime/basic.html
1
/* 为 `i` 和 `borrow2` 标注合适的生命周期范围 */
// `i` 拥有最长的生命周期,因为它的作用域完整的包含了 `borrow1` 和 `borrow2` 。
// 而 `borrow1` 和 `borrow2` 的生命周期并无关联,因为它们的作用域没有重叠
fn main() {
let i = 3;
{
let borrow1 = &i; // `borrow1` 生命周期开始. ──┐
// │
println!("borrow1: {}", borrow1); // │
} // `borrow1` 生命周期结束. ──────────────────────────────────┘
{
let borrow2 = &i;
println!("borrow2: {}", borrow2);
}
}
我就不标注了,i
的生命周期从main函数开始到结束。
2
/* 像上面的示例一样,为 `r` 和 `x` 标准生命周期,然后从生命周期的角度. */
fn main() {
{
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // |
} // ---------+
}
和上面的代码一样,这段代码是无法通过编译的,r
的生命周期大于其引用的x
。这意味着一旦x
被释放,r
将成为一个悬垂引用。
3
/* 添加合适的生命周期标注,让下面的代码工作 */
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
fn main() {}
答案:
fn longest<'a, 'b:'a>(x:&'a str, y:&'b str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
原则1:每一个引用参数都会获得独特的生命周期
然后没有了,此时编译器无法推导出返回值引用的生命周期,这就需要我们手动标注。因为我们将返回值的生命周期标注为a
,就必须让返回值的生命周期小于入参的(否则入参被释放了,就出现悬垂引用的问题了)
另外,类似的生命周期问题在C++中也是存在的:
int &larger(int &a, int &b)
{
return a > b ? a : b;
}
int main()
{
int a = 1;
int& res = a;
{
int b = 2;
res = larger(a, b);
}
cout << res << endl;
return 0;
}
注意,C++是强类型语言,因此
res
的类型必须提前声明(即使用auto,那也得是auto&)。另外,C++中的引用声明时就必须初始化。
当b为更大值时,这里就会出现悬垂引用的问题。但在我编译这段代码并反复运行时,我没有看到异常输出。我的C++水平还没法解释为什么。
4
使用三种方法修复下面的错误
fn invalid_output<'a>() -> &'a String {
&String::from("foo")
}
fn main() {
}
第一种当然是最原始的方法,转移所有权:
fn invalid_output() -> String {
String::from("foo")
}
新增一个入参也是不错的选择,甚至可以省略生命周期标注符号:
fn invalid_output(s: &String) -> &String {
s
}
第三种我倒是没想到,看了答案才意识到,我也可以直接返回硬编码的&str
:
fn invalid_output() -> &'static str {
"foo"
}
5
// `print_refs` 有两个引用参数,它们的生命周期 `'a` 和 `'b` 至少得跟函数活得一样久
fn print_refs<'a, 'b>(x: &'a i32, y: &'b i32) {
println!("x is {} and y is {}", x, y);
}
/* 让下面的代码工作 */
fn failed_borrow<'a>() {
let _x = 12;
// ERROR: `_x` 活得不够久does not live long enough
let y: &'a i32 = &_x;
// 在函数内使用 `'a` 将会报错,原因是 `&_x` 的生命周期显然比 `'a` 要小
// 你不能将一个小的生命周期强转成大的
}
fn main() {
let (four, nine) = (4, 9);
print_refs(&four, &nine);
// 这里,four 和 nice 的生命周期必须要比函数 print_refs 长
failed_borrow();
// `failed_borrow` 没有传入任何引用去限制生命周期 `'a`,因此,此时的 `'a` 生命周期是没有任何限制的,它默认是 `'static`
}
Rust处理这类问题的一个基本原则是:调用者的生命周期一定要比被调用的短,否则就会出现空的调用。
fn failed_borrow<'a>() {
let _x = 12;
// ERROR: `_x` 活得不够久does not live long enough
let y: & i32 = &_x;
// 在函数内使用 `'a` 将会报错,原因是 `&_x` 的生命周期显然比 `'a` 要小
// 你不能将一个小的生命周期强转成大的
}
6
/* 增加合适的生命周期标准,让代码工作 */
// `i32` 的引用必须比 `Borrowed` 活得更久
#[derive(Debug)]
struct Borrowed(&i32);
// 类似的,下面两个引用也必须比结构体 `NamedBorrowed` 活得更久
#[derive(Debug)]
struct NamedBorrowed {
x: &i32,
y: &i32,
}
#[derive(Debug)]
enum Either {
Num(i32),
Ref(&i32),
}
fn main() {
let x = 18;
let y = 15;
let single = Borrowed(&x);
let double = NamedBorrowed { x: &x, y: &y };
let reference = Either::Ref(&x);
let number = Either::Num(y);
println!("x is borrowed in {:?}", single);
println!("x and y are borrowed in {:?}", double);
println!("x is borrowed in {:?}", reference);
println!("y is *not* borrowed in {:?}", number);
}
我得去查查Rust有没有什么编码规范,规定了生命周期的命名。
#[derive(Debug)]
struct Borrowed<'a>(&'a i32);
// 类似的,下面两个引用也必须比结构体 `NamedBorrowed` 活得更久
#[derive(Debug)]
struct NamedBorrowed<'b> {
x: &'b i32,
y: &'b i32,
}
#[derive(Debug)]
enum Either <'c>{
Num(i32),
Ref(&'c i32),
}
7
/* 让代码工作 */
#[derive(Debug)]
struct NoCopyType {}
#[derive(Debug)]
struct Example<'a, 'b> {
a: &'a u32,
b: &'b NoCopyType
}
fn main()
{
let var_a = 35;
let example: Example;
{
let var_b = NoCopyType {};
/* 修复错误 */
example = Example { a: &var_a, b: &var_b };
}
println!("(Success!) {:?}", example);
}
b
的生命周期太短了,打印example
时b
已经被释放了。
fn main()
{
let var_a = 35;
let example: Example;
let var_b = NoCopyType {};
{
/* 修复错误 */
example = Example { a: &var_a, b: &var_b };
}
println!("(Success!) {:?}", example);
}
8
#[derive(Debug)]
struct NoCopyType {}
#[derive(Debug)]
#[allow(dead_code)]
struct Example<'a, 'b> {
a: &'a u32,
b: &'b NoCopyType
}
/* 修复函数的签名 */
fn fix_me(foo: &Example) -> &NoCopyType
{ foo.b }
fn main()
{
let no_copy = NoCopyType {};
let example = Example { a: &1, b: &no_copy };
fix_me(&example);
println!("Success!")
}
加个生命周期标识符就行了,至于是否真能活到那时候,谁知道。
fn fix_me<'a>(foo: &'a Example) -> &'a NoCopyType
{ foo.b }
9
/* 添加合适的生命周期让下面代码工作 */
struct ImportantExcerpt {
part: &str,
}
impl ImportantExcerpt {
fn level(&'a self) -> i32 {
3
}
}
fn main() {}
答案
struct ImportantExcerpt<'a> {
part: &'a str,
}
impl<'a> ImportantExcerpt<'a> {
fn level(&'a self) -> i32 {
3
}
}
10
/* 移除所有可以消除的生命周期标注 */
fn nput<'a>(x: &'a i32) {
println!("`annotated_input`: {}", x);
}
fn pass<'a>(x: &'a i32) -> &'a i32 { x }
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
x
}
struct Owner(i32);
impl Owner {
fn add_one<'a>(&'a mut self) { self.0 += 1; }
fn print<'a>(&'a self) {
println!("`print`: {}", self.0);
}
}
struct Person<'a> {
age: u8,
name: &'a str,
}
enum Either<'a> {
Num(i32),
Ref(&'a i32),
}
fn main() {}
由于入参只有一个引用,引用只有一个生命周期,第一个、第二个消除:
fn nput(x: &i32) {
println!("`annotated_input`: {}", x);
}
fn pass(x: &i32) -> &i32 { x }
参数含有self时(即方法),self的生命周期会赋给全部输出,这里也消除:
struct Owner(i32);
impl Owner {
fn add_one(&mut self) { self.0 += 1; }
fn print(&self) {
println!("`print`: {}", self.0);
}
}