4 可变引用与容器

原文链接: https://tfpk.github.io/lifetimekata/chapter_4.html

就生命周期省略而言,可变引用的工作方式与普通引用完全相同。不过我们单独设一章来讲它们,是因为如果你持有可变引用,即使没有返回值,也可能需要向编译器说明生命周期。

例如,来看下面这个例子:

1
2
3
fn insert_value(my_vec: &mut Vec<&i32>, value: &i32) {
    my_vec.push(value);
}

我们没有返回任何东西;所以生命周期不重要,对吧?

不幸的是,生命周期仍然很重要。引用 value 实际上需要与向量中的内容存活同样长的时间。如果不是这样,向量里就可能包含无效引用。例如,在下面这个场景中会发生什么?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fn insert_value(my_vec: &mut Vec<&i32>, value: &i32) {
    my_vec.push(value);
}

fn main() {
    let x = 1;
    let my_vec = vec![&x];
    {
        let y = 2;
        insert_value(&mut my_vec, &y);
    }
    println!("{my_vec:?}");
}

当我们尝试打印向量时,上面例子中对 y 的引用已经悬空了!

我们可以用生命周期来确保这两个引用存活同样长的时间:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fn insert_value<'vec_lifetime, 'contents_lifetime>(my_vec: &'vec_lifetime mut Vec<&'contents_lifetime i32>, value: &'contents_lifetime i32) {
    my_vec.push(value)
}
fn main(){
    let mut my_vec = vec![];
    let val1 = 1;
    let val2 = 2;
    
    insert_value(&mut my_vec, &val1);
    insert_value(&mut my_vec, &val2);
    
    println!("{my_vec:?}");
}

这个签名表明存在两个生命周期:

  • 'vec_lifetime:我们传给函数的向量需要存活一段时间。
  • 'contents_lifetime:向量的内容需要存活一段时间。重要的是,我们要插入的新 value 需要与向量中的内容存活同样长的时间。如果不是这样,你最终会得到一个包含无效引用的向量。

我们真的需要两个生命周期吗?

你可能会想,如果不提供两个生命周期会怎样。只用一个生命周期可以吗?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn insert_value<'one_lifetime>(my_vec: &'one_lifetime mut Vec<&'one_lifetime i32>, value: &'one_lifetime i32) {
    my_vec.push(value)
}

fn main(){
    let mut my_vec: Vec<&i32> = vec![];
    let val1 = 1;
    let val2 = 2;
    
    insert_value(&mut my_vec, &val1);
    insert_value(&mut my_vec, &val2);
    
    println!("{my_vec:?}");
}

不行。我们会得到两个错误。先看第一个:

error[E0499]: cannot borrow `my_vec` as mutable more than once at a time
  --> /tmp/rust.rs:11:18
   |
10 |     insert_value(&mut my_vec, &val1);
   |                  ----------- first mutable borrow occurs here
11 |     insert_value(&mut my_vec, &val2);
   |                  ^^^^^^^^^^^
   |                  |
   |                  second mutable borrow occurs here
   |                  first borrow later used here

这看起来很奇怪——为什么不能借用 my_vec?

好吧,让我们看看编译器眼中的情况:

&val 需要与 my_vec 存在的时间一样长:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# fn insert_value<'one_lifetime>(my_vec: &'one_lifetime mut Vec<&'one_lifetime i32>, value: &'one_lifetime i32) {
#     my_vec.push(value)
# }
# 
# fn main(){
    let mut my_vec: Vec<&i32> = vec![];
    let val1 = 1;
    let val2 = 2;
    
    insert_value(&mut my_vec, &val1); // \
    insert_value(&mut my_vec, &val2); // | - &val1 需要存活这么久。
                                      // |
    println!("{my_vec:?}");           // /
# }

而 &mut my_vec 只需要在 insert_value 执行期间存活。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# fn insert_value<'one_lifetime>(my_vec: &'one_lifetime mut Vec<&'one_lifetime i32>, value: &'one_lifetime i32) {
#     my_vec.push(value)
# }
# 
# fn main(){
    let mut my_vec: Vec<&i32> = vec![];
    let val1 = 1;
    let val2 = 2;
    
    insert_value(&mut my_vec, &val1); // <- &mut my_vec 只需要存活这么久。
    insert_value(&mut my_vec, &val2); 
    
    println!("{my_vec:?}");
# }

但是,我们告诉编译器 &val1 和 &mut my_vec 的借用需要共享同一个生命周期。因此编译器会延长 &mut my_vec 的借用以确保它们共享生命周期: 它看到如果让 &mut my_vec 与 &val1 存活一样久,就会有下面这段代码区域:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# fn insert_value<'one_lifetime>(my_vec: &'one_lifetime mut Vec<&'one_lifetime i32>, value: &'one_lifetime i32) {
#     my_vec.push(value)
# }
# 
# fn main(){
    let mut my_vec: Vec<&i32> = vec![];
    let val1 = 1;
    let val2 = 2;
    
    insert_value(&mut my_vec, &val1); // \
    insert_value(&mut my_vec, &val2); // | - 'one_lifetime 必须是这段代码区域。
                                      // |
    println!("{my_vec:?}");           // /
# }

这没问题。但现在编译器执行到下一行,发现你又想借用 &mut my_vec。

编译器已经决定 &mut my_vec 必须存在到函数结束。 所以现在,你要求它创建两个可变引用……而这是不允许的。

因此编译器会报错——你不允许再次借用 &mut my_vec。

为什么两个生命周期能修复这个错误?

在阅读本节之前先想一想——为什么两个生命周期能解决这个问题?

之前,编译器不得不认定 &mut my_vec 和 &val1 共享一个生命周期。 换句话说,它们彼此存活同样长。

通过使用两个生命周期,我们告诉编译器 &mut my_vec 和 &val1 不一定需要存活同样长的时间。于是, 编译器会找到以下生命周期:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn insert_value<'vec_lifetime, 'contents_lifetime>(my_vec: &'vec_lifetime mut Vec<&'contents_lifetime i32>, value: &'contents_lifetime i32) {
    my_vec.push(value)
}

fn main(){
    let mut my_vec: Vec<&i32> = vec![];
    let val1 = 1;
    let val2 = 2;
    
    insert_value(&mut my_vec, &val1); // <- 'vec_lifetime \
    insert_value(&mut my_vec, &val2); //                  | 'contents_lifetime
                                      //                  |
    println!("{my_vec:?}");           //                  /
}

练习第一部分:另一个错误

首先,让我们看看上一节得到的另一个错误:

error[E0502]: cannot borrow `my_vec` as immutable because it is also borrowed as mutable
  --> /tmp/rust.rs:13:16
   |
10 |     insert_value(&mut my_vec, &val1);
   |                  ----------- mutable borrow occurs here
...
13 |     println!("{my_vec:?}");
   |                ^^^^^^
   |                |
   |                immutable borrow occurs here
   |                mutable borrow later used here
   |

你能解释为什么会出现这个错误吗?用 50 个词或更少写出来。

练习第二部分:自己动手写

为练习中的函数添加合适的生命周期。

最后修改 August 23, 2026: 更新 (499855b16)