文章

Rust 基本功 -- Option 与 Box 的 as_mut 实现

Rust 基本功 -- Option 与 Box 的 as_mut 实现

Rust 基本功

一、关于 Prelude 机制

在使用 Rust 的 OptionBox 时, 我们不需要手动导入相关的模块, 它们都是 Rust 标准库的一部分, 但是它们有着不同的可见性机制。 事实上这些数据结构是通过 Prelude 机制进行自动导入的

std::prelude::v1 包含的内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 标准库位置:std::prelude::v1
pub use crate::marker::{Send, Sync, Sized, Unpin};
pub use crate::ops::{Drop, Fn, FnMut, FnOnce};
pub use crate::mem::drop;
pub use crate::boxed::Box;           // 这就是为什么 Box 不需要导入
pub use crate::borrow::ToOwned;
pub use crate::clone::Clone;
pub use crate::cmp::{PartialEq, PartialOrd, Eq, Ord};
pub use crate::convert::{AsRef, AsMut, Into, From};
pub use crate::default::Default;
pub use crate::iter::{Iterator, Extend, IntoIterator};
pub use crate::option::Option::{self, Some, None};  // Option 及其变体
pub use crate::result::Result::{self, Ok, Err};     // Result 及其变体
pub use crate::string::{String, ToString};
pub use crate::vec::Vec;

举个具体的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 实际位置(你通常不需要这样写)
use std::boxed::Box;
use std::option::Option;
use std::vec::Vec;
use std::collections::HashMap; // 这个需要手动导入!

// HashMap 不在 prelude 中,需要手动导入
use std::collections::HashMap;

fn example() {
    let map = HashMap::new(); // 需要上面的 use 语句
    let opt = Option::Some(42); // 或直接用 Some(42)
    let boxed = Box::new(42);
}

二、Option::as_mut() 源码详解

1
2
3
4
5
6
7
8
9
10
#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_stable(feature = "const_option", since = "1.83.0")]
pub const fn as_mut(&mut self) -> Option<&mut T> {
    match *self {
        // ref mut 含义: 在模式匹配中创建可变引用, 而不是移动值
        Some(ref mut x) => Some(x),
        None => None,
    }
}

模式匹配中 ref mut 是什么呢? ref mut 是一个模式匹配时的借用操作符, 作用是在模式匹配中创建可变引用, 而不是移动值.

下面分别针对三种情况来探讨下编译器的表现:

情况一: 从源码中删除掉 ref mut (编译错误)

1
2
3
4
5
6
7
8
9
// ❌ 这样写会编译失败
impl<T> Option<T> {
    pub fn as_mut_broken(&mut self) -> Option<&mut T> {
        match *self {
            Some(x) => Some(&mut x), // x 是 T 类型, 此处发生编译错误!❌
            None => None,
        }
    }
}

❌ 编译错误的原因:

  • x 是通过 move 移动获得的 T 类型值
  • &mut x 创建了对局部变量 x 的引用
  • 该局部变量引用在函数结束时就失效了

情况二: 只使用 ref (只读引用), 不加 mut

1
2
3
4
5
6
// ✅ 可以编译,但返回类型不匹配
match *self {
    Some(ref x) => Some(x), // 此处 x 类型是 &T,不是 &mut T
    None => None,
}
// 返回 Option<&T>,不是我们想要的 Option<&mut T>

情况三: 正确使用 ref mut

1
2
3
4
5
6
// ✅ 正确的实现
match *self {
    Some(ref mut x) => Some(x), // 此时 x 的类型是 &mut T
    None => None,
}
// 返回 Option<&mut T>

三、Box::as_mut() 源码详解

1
2
3
4
5
6
#[stable(since = "1.5.0", feature = "smart_ptr_as_ref")]
impl<T: ?Sized, A: Allocator> AsMut<T> for Box<T, A> {
    fn as_mut(&mut self) -> &mut T {
        &mut **self
    }
}

逐步分析表达式 &mut **self:

  1. self 的类型: &mut Box<T, A>, 表示对 Box 的可变引用
  2. 第一个 * 用来解引用 Box 的引用: &mut Box<T, A> -> Box<T,A>
  3. 第二个 * 用来解引用 Box 本身: Box<T,A> -> T (Box 指向的实际数据)
  4. &mut 创建可变引用: T -> &mut T

下面是 AI 绘制的简单的内存视图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 内存布局示意
//
// 栈上:
// boxed_value: Box<i32> 
// ┌─────────────┐
// │ ptr: 0x1000 │ ──┐
// │ alloc: ...  │   │
// └─────────────┘   │
//                   │
// 堆上:             │
// 0x1000:           │
// ┌───────────┐ ←───┘
// │    42     │
// └───────────┘
//
// &mut **self 的执行:
// 1. self: &mut Box<i32> (指向栈上的 Box)
// 2. *self: Box<i32> (栈上的 Box 值)
// 3. **self: i32 (堆上的实际值)
// 4. &mut **self: &mut i32 (指向堆上值的可变引用)

Box::as_mut() 使用方式:

1
2
let mut boxed: Box<T> = Box::new(T::new());
let res: &mut T = boxed.as_mut();
  1. boxed 的类型是可变的 Box<T>
  2. as_mut() 需要 &mut self, 即 &mut Box<T>
  3. Rust 自动从 mut boxed 创建 &mut boxed
本文由作者按照 CC BY 4.0 进行授权