跳转至

常见的生命周期错误

  • 示例1
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
struct S<'a> {
    x: &'a i32,
    y: &'a i32
}

fn main() {
    let x = 10;
    let r;
    {
        let y = 20;
        {
            let s = S { x: &x, y: &y };
            r = s.x;
        }
    }
    println!("{} {}", x, r);
}

错误分析

1
2
3
4
5
6
7
8
   |
12 |             let s = S { x: &x, y: &y };
   |                                   ^^ borrowed value does not live long enough
...
15 |     }
   |     - `y` dropped here while still borrowed
16 |     println!("{} {}", x, r);
   |                          - borrow later used here

实际上,上面这段代码错误的原因是S<'a>中的两个字段的生命周期都是'a,这导致let s = S { x: &x, y: &y };r = s.x两个表达式存在歧义,前者要求x的生命周期和y一致,这个可以通过协变达成。但是后者要求s.x的生命周期要大于y。 这就尴尬了,你是没办法找出这样的一个生命周期大于y并且等于y。所以上面报错了。

解决方案

只需要将S的两个字段用不同的生命周期标注即可。这样就可以满足上面代码对于生命周期的要求。

1
2
3
4
struct S<'a, 'b> {
    x: &'a i32,
    y: &'b i32
}
  • 示例2
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  struct ByteIter<'a> {
    remainder: &'a [u8]
  }

  impl<'a> ByteIter<'a> {
      fn next(&mut self) -> Option<&u8> {
          if self.remainder.is_empty() {
              None
          } else {
              let byte = &self.remainder[0];
              self.remainder = &self.remainder[1..];
              Some(byte)
          }
      }
  }

  fn main() {
      let mut bytes = ByteIter { remainder: b"1" };
      let byte_1 = bytes.next();
      let byte_2 = bytes.next();
      if byte_1 == byte_2 {
          // do something
      }
  }

错误分析

1
2
3
4
5
6
7
8
9
error[E0499]: cannot borrow `bytes` as mutable more than once at a time
  --> src/main.rs:20:18
   |
19 |     let byte_1 = bytes.next();
   |                  ----- first mutable borrow occurs here
20 |     let byte_2 = bytes.next();
   |                  ^^^^^ second mutable borrow occurs here
21 |     if byte_1 == byte_2 {
   |        ------ first borrow later used here

错误的原因是因为rust默认给next添加了生命周期注解,展开后如下:

1
  fn next(&'b mut self) -> Option<&'b u8>

这导致编译器认为返回的Option<&'b u8>是对&b mut self的引用,而这里是一个典型的&mut降级为&的场景。当第二次再调用next方法的时候会再次创建&mut self引用。 我们都知道这是没办法创建的,因为&mut&不能同时出现。

解决方案

只需要将Option<&u8>改成Option<&'a u8>即可,让返回值指向remainder,那么编译器就不会认为是对&mut self的引用,也自然不会有问题了。

  • 示例3
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#[derive(Debug)]
struct NumRef<'a>(&'a i32);

impl<'a> NumRef<'a> {
    // 我定义的泛型结构体以 'a 为参数,这意味着我也需要给方法的参数
    // 标注为 'a 生命周期,对吗?(答案:错)
    fn some_method(&'a mut self) {}
}

fn main() {
    let mut num_ref = NumRef(&5);
    num_ref.some_method(); // 可变借用 num_ref 直至其生命周期结束
    num_ref.some_method(); // 编译错误
    println!("{:?}", num_ref); // 同样编译错误
}

错误分析

上面代码错误的原因是因为some_method方法被显示设置了生命注解'a导致在调用some_method的时候误认为&'a mut self引用了num_ref,而num_ref的生命周期是直到程序结束,因此 some_method的调用导致&mut self一直被持有直到程序结束,因此也无法再调用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
error[E0499]: cannot borrow `num_ref` as mutable more than once at a time
  --> src/main.rs:13:5
   |
12 |     num_ref.some_method(); // 可变借用 num_ref 直至其生命周期结束
   |     ------- first mutable borrow occurs here
13 |     num_ref.some_method(); // 编译错误
   |     ^^^^^^^
   |     |
   |     second mutable borrow occurs here
   |     first borrow later used here

解决方案

&mut selfnum_ref的关系解耦,不要用相同的生命周期,使用编译器默认给我some_method添加的生命周期即可。

1
2
3
impl<'a> NumRef<'a> {
  fn some_method(&mut self) {}
}
  • 示例4
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
use std::fmt::Display;

fn dynamic_thread_print(t: Box<dyn Display + Send>) {
    std::thread::spawn(move || {
        println!("{}", t);
    }).join();
}

fn static_thread_print<T: Display + Send>(t: T) {
    std::thread::spawn(move || {
        println!("{}", t);
    }).join();
}

错误分析

1
2
3
4
5
6
7
error[E0310]: the parameter type `T` may not live long enough
  --> src/main.rs:10:5
   |
9  | fn static_thread_print<T: Display + Send>(t: T) {
   |                        -- help: consider adding an explicit lifetime bound...: `T: 'static +`
10 |     std::thread::spawn(move || {
   |     ^^^^^^^^^^^^^^^^^^ ...so that the type `[closure@src/main.rs:10:24: 12:6]` will meet its required lifetime bounds

这是因为std::thread::spawn要求其内部捕获的类型需要满足'static约束,因此会编译出错,要求给T添加'static约束,但是为什么dynamic_thread_print函数中没有要求t有'static的生命周期约束呢? 这是因为编译器给我们默认添加了。对于所有的trait对象编译器有着一套规则来给其添加生命周期约束,下面是具体的规则。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
use std::cell::Ref;

trait Trait {}

// 展开前
type T1 = Box<dyn Trait>;
// 展开后,Box<T> 没有对 T 的生命周期约束,所以推导为 'static
type T2 = Box<dyn Trait + 'static>;

// 展开前
impl dyn Trait {}
// 展开后
impl dyn Trait + 'static {}

// 展开前
type T3<'a> = &'a dyn Trait;
// 展开后,&'a T 要求 T: 'a, 所以推导为 'a
type T4<'a> = &'a (dyn Trait + 'a);

// 展开前
type T5<'a> = Ref<'a, dyn Trait>;
// 展开后,Ref<'a, T> 要求 T: 'a, 所以推导为 'a
type T6<'a> = Ref<'a, dyn Trait + 'a>;

trait GenericTrait<'a>: 'a {}

// 展开前
type T7<'a> = Box<dyn GenericTrait<'a>>;
// 展开后
type T8<'a> = Box<dyn GenericTrait<'a> + 'a>;

// 展开前
impl<'a> dyn GenericTrait<'a> {}
// 展开后
impl<'a> dyn GenericTrait<'a> + 'a {}

解决方案

按照编译器给的提示给T添加生命周期注解即可。

  • 示例5
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct Has<'lifetime> {
    lifetime: &'lifetime str,
}

fn main() {
    let long = String::from("long");
    let mut has = Has { lifetime: &long };
    assert_eq!(has.lifetime, "long");

    // 这个代码块逻辑上永远不会被执行
    if false {
        let short = String::from("short");
        // “转换到” 短的生命周期
        has.lifetime = &short;
        assert_eq!(has.lifetime, "short");

        // “转换回” 长的生命周期(实际是并不是)
        has.lifetime = &long;
        assert_eq!(has.lifetime, "long");
        // `short` 变量在这里 drop
    }

    // 还是编译失败, `short` 在 drop 后仍旧处于 “借用” 状态
    assert_eq!(has.lifetime, "long");
}

错误分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
error[E0597]: `short` does not live long enough
  --> src/main.rs:14:24
   |
14 |         has.lifetime = &short;
   |                        ^^^^^^ borrowed value does not live long enough
...
21 |     }
   |     - `short` dropped here while still borrowed
...
24 |     assert_eq!(has.lifetime, "long");
   |     --------------------------------- borrow later used here

错误的原因是因为Rust中的生命周期是在编译时一旦确定就无法更改的,虽然我们可以让短的生命周期指向长的,但是这一切都是在编译期确定的,Rust编译器会分析代码中任何的执行流,并选择一个最短的生命周期。 例如下面这段代码,Rust在编译时决定了lifetime的生命周期和short一样长。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct Has<'lifetime> {
    lifetime: &'lifetime str,
}

fn main() {
    let short = String::from("short");
    let mut has = Has { lifetime: &short };
    assert_eq!(has.lifetime, "short");
    has.lifetime = &"long";
    assert_eq!(has.lifetime, "long");
}

Reference