Module core::option

1.0.0 · source ·
Expand description

可选值。

类型 Option 表示一个可选值:每个 Option 均为 Some 并包含一个值,或者为 None,但不包含。 Option 类型在 Rust 代码中非常常见,因为它们有多种用途:

  • 初始值
  • 未在整个输入范围内定义的函数的返回值 (部分函数)
  • 返回值,用于报告否则将报告简单错误的错误,其中错误返回 None
  • 可选的结构体字段
  • 可借用或 “taken” 的结构体字段
  • 可选的函数参数
  • 可空指针
  • 从困难的情况中交换东西

通常将 Option 与模式匹配配对,以查询值的存在并采取措施,始终考虑 None 的情况。

fn divide(numerator: f64, denominator: f64) -> Option<f64> {
    if denominator == 0.0 {
        None
    } else {
        Some(numerator / denominator)
    }
}

// 函数的返回值是一个选项
let result = divide(2.0, 3.0);

// 模式匹配以获取值
match result {
    // 该划分有效
    Some(x) => println!("Result: {x}"),
    // 划分无效
    None    => println!("Cannot divide by 0"),
}
Run

选项和指针 (“nullable” 指针)

Rust 的指针类型必须始终指向有效位置。没有 “null” 引用。相反,Rust 有 optional 指针,就像可选的拥有所有权的 box,Option<Box<T>>

以下示例使用 Option 创建 i32 的可选 box。 注意,为了使用内部的 i32 值,check_optional 函数首先需要使用模式匹配来确定 box 是否有值 (即它是 Some(...)) 或没有 (None)。

let optional = None;
check_optional(optional);

let optional = Some(Box::new(9000));
check_optional(optional);

fn check_optional(optional: Option<Box<i32>>) {
    match optional {
        Some(p) => println!("has value {p}"),
        None => println!("has no value"),
    }
}
Run

问号运算符,?

Result 类型类似,在编写调用许多返回 Option 类型的函数的代码时,处理 Some/None 可能会很乏味。问号运算符 ? 隐藏了一些在调用栈上传播值的样板。

它将替换为:

fn add_last_numbers(stack: &mut Vec<i32>) -> Option<i32> {
    let a = stack.pop();
    let b = stack.pop();

    match (a, b) {
        (Some(x), Some(y)) => Some(x + y),
        _ => None,
    }
}
Run

有了这个:

fn add_last_numbers(stack: &mut Vec<i32>) -> Option<i32> {
    Some(stack.pop()? + stack.pop()?)
}
Run

好多了!

? 结尾的表达式将导致 Some 的展开值,除非结果为 None,在这种情况下,None 会从封闭的函数中提前返回。

? 可以用在返回 Option 的函数中,因为它提供了 None 的提前返回。

Representation

Rust 保证优化以下 T 类型,以使 Option<T> 具有与 T 相同的大小:

这称为 “空指针优化” 或 NPO。

对于上述情况,可以进一步保证,可以从 TOption<T> 的所有有效值以及从 Some::<T>(_)T 的所有有效值 mem::transmute (但是将 None::<T> 转换为 T 是未定义的行为)。

方法概述

除了使用模式匹配,Option 还提供了多种不同的方法。

查询变体

如果 Option 分别为 SomeNone,则 is_someis_none 方法返回 true

用于处理引用的适配器

提取包含的值

当它是 Some 变体时,这些方法提取 Option<T> 中包含的值。如果 OptionNone

转换包含的值

这些方法将 Option 转换为 Result

这些方法转换了 Some 变体:

这些方法将 Option<T> 转换为可能不同类型 U 的值:

  • map_or 将提供的函数应用于 Some 的包含值,或者如果 Option 是返回提供的默认值 None
  • map_or_else applies the provided function to the contained value of Some, or returns the result of evaluating the provided fallback function if the Option is None

这些方法结合了两个 Option 值的 Some 变体:

布尔运算符

这些方法将 Option 视为布尔值,其中 Some 的作用类似于 true,而 None 的作用类似于 false。这些方法有两类:一类以 Option 作为输入,一类以函数作为输入 (延迟评估)。

andorxor 方法将另一个 Option 作为输入,并生成一个 Option 作为输出。只有 and 方法可以生成具有与 Option<T> 不同的内部类型 UOption<U> 值。

methodselfinputoutput
andNone(ignored)None
andSome(x)NoneNone
andSome(x)Some(y)Some(y)
orNoneNoneNone
orNoneSome(y)Some(y)
orSome(x)(ignored)Some(x)
xorNoneNoneNone
xorNoneSome(y)Some(y)
xorSome(x)NoneSome(x)
xorSome(x)Some(y)None

and_thenor_else 方法将函数作为输入,并且仅在需要产生新值时才评估函数。只有 and_then 方法可以生成具有与 Option<T> 不同的内部类型 UOption<U> 值。

methodselffunction inputfunction resultoutput
and_thenNone(not provided)(not evaluated)None
and_thenSome(x)xNoneNone
and_thenSome(x)xSome(y)Some(y)
or_elseNone(not provided)NoneNone
or_elseNone(not provided)Some(y)Some(y)
or_elseSome(x)(not provided)(not evaluated)Some(x)

这是在方法调用管道中使用 and_thenor 等方法的示例。管道的早期阶段通过不变的失败值 (None),并继续处理成功值 (Some)。 最后,如果 or 收到 None,它会替换一条错误消息。

let mut bt = BTreeMap::new();
bt.insert(20u8, "foo");
bt.insert(42u8, "bar");
let res = [0u8, 1, 11, 200, 22]
    .into_iter()
    .map(|x| {
        // `checked_sub()` 出错时返回 `None`
        x.checked_sub(1)
            // 与 `checked_mul()` 相同
            .and_then(|x| x.checked_mul(2))
            // `BTreeMap::get` 出错时返回 `None`
            .and_then(|x| bt.get(&x))
            // 如果到目前为止我们有 `None`,则替换一条错误消息
            .or(Some(&"error!"))
            .copied()
            // 不会 panic 因为我们无条件使用了上面的 `Some`
            .unwrap()
    })
    .collect::<Vec<_>>();
assert_eq!(res, ["error!", "error!", "foo", "error!", "bar"]);
Run

比较运算符

如果 T 实现 PartialOrd,那么 Option<T> 将派生其 PartialOrd 实现。使用此顺序,None 的比较比任何 Some 都小,两个 Some 的比较方式与其在 T 中包含的值相同。 如果 T 也实现了 Ord,那么 Option<T> 也是如此。

assert!(None < Some(0));
assert!(Some(0) < Some(1));
Run

迭代结束 Option

可以对 Option 进行迭代。如果您需要一个条件为空的迭代器,这会很有帮助。迭代器将产生单个值 (当 OptionSome 时),或不产生任何值 (当 OptionNone 时)。 例如,如果 OptionSome(v),则 into_iter 的作用类似于 once(v); 如果 OptionNone,则它的作用类似于 empty()

Option<T> 上的迭代器分为三种类型:

  • into_iter 消耗 Option 并产生包含的值
  • iter 对包含的值产生类型为 &T 的不可变引用
  • iter_mut 产生一个 &mut T 类型的可变引用到包含的值

Option 上的迭代器在链接迭代器时很有用,例如,有条件地插入项。 (并不总是需要显式调用迭代器构造函数:许多接受其他迭代器的 Iterator 方法也将接受实现 IntoIterator 的可迭代类型,其中包括 Option。)

let yep = Some(42);
let nope = None;
// chain() 已经调用 into_iter(),所以我们不必这样做
let nums: Vec<i32> = (0..4).chain(yep).chain(4..8).collect();
assert_eq!(nums, [0, 1, 2, 3, 42, 4, 5, 6, 7]);
let nums: Vec<i32> = (0..4).chain(nope).chain(4..8).collect();
assert_eq!(nums, [0, 1, 2, 3, 4, 5, 6, 7]);
Run

以这种方式链接迭代器的一个原因是,返回 impl Iterator 的函数必须使所有可能的返回值都具有相同的具体类型。链接一个迭代的 Option 可以帮助解决这个问题。

fn make_iter(do_insert: bool) -> impl Iterator<Item = i32> {
    // 显式返回来说明返回类型匹配
    match do_insert {
        true => return (0..4).chain(Some(42)).chain(4..8),
        false => return (0..4).chain(None).chain(4..8),
    }
}
println!("{:?}", make_iter(true).collect::<Vec<_>>());
println!("{:?}", make_iter(false).collect::<Vec<_>>());
Run

如果我们尝试做同样的事情,但是使用 once()empty(),我们就不能再返回 impl Iterator,因为返回值的具体类型不同。

// 这不会编译,因为函数的所有可能返回必须具有相同的具体类型。
//
fn make_iter(do_insert: bool) -> impl Iterator<Item = i32> {
    // 显式返回以说明返回类型不匹配
    match do_insert {
        true => return (0..4).chain(once(42)).chain(4..8),
        false => return (0..4).chain(empty()).chain(4..8),
    }
}
Run

收集到 Option

Option 实现了 FromIterator trait,它允许将 Option 值上的迭代器收集到原始 Option 值的每个包含值的集合的 Option 中,或者如果任何元素是 None,则为 None

let v = [Some(2), Some(4), None, Some(8)];
let res: Option<Vec<_>> = v.into_iter().collect();
assert_eq!(res, None);
let v = [Some(2), Some(4), Some(8)];
let res: Option<Vec<_>> = v.into_iter().collect();
assert_eq!(res, Some(vec![2, 4, 8]));
Run

Option 还实现了 ProductSum traits,允许对 Option 值的迭代器提供 productsum 方法。

let v = [None, Some(1), Some(2), Some(3)];
let res: Option<i32> = v.into_iter().sum();
assert_eq!(res, None);
let v = [Some(1), Some(2), Some(21)];
let res: Option<i32> = v.into_iter().product();
assert_eq!(res, Some(42));
Run

就地修改 Option

这些方法返回对包含的值的可变引用 Option<T>:

这些方法转移包含的值的所有权 Option:

Examples

Option 上的基本模式匹配:

let msg = Some("howdy");

// 获取对所包含字符串的引用
if let Some(m) = &msg {
    println!("{}", *m);
}

// 删除包含的字符串,销毁 Option
let unwrapped_msg = msg.unwrap_or("default message");
Run

循环前将结果初始化为 None

enum Kingdom { Plant(u32, &'static str), Animal(u32, &'static str) }

// 要搜索的数据列表。
let all_the_big_things = [
    Kingdom::Plant(250, "redwood"),
    Kingdom::Plant(230, "noble fir"),
    Kingdom::Plant(229, "sugar pine"),
    Kingdom::Animal(25, "blue whale"),
    Kingdom::Animal(19, "fin whale"),
    Kingdom::Animal(15, "north pacific right whale"),
];

// 我们将搜索最大的动物的名称,但首先要获取 `None`。
//
let mut name_of_biggest_animal = None;
let mut size_of_biggest_animal = 0;
for big_thing in &all_the_big_things {
    match *big_thing {
        Kingdom::Animal(size, name) if size > size_of_biggest_animal => {
            // 现在我们找到了一些大动物的名字
            size_of_biggest_animal = size;
            name_of_biggest_animal = Some(name);
        }
        Kingdom::Animal(..) | Kingdom::Plant(..) => ()
    }
}

match name_of_biggest_animal {
    Some(name) => println!("the biggest animal is {name}"),
    None => println!("there are no animals :("),
}
Run

  1. 这对于任何其他 ABI 仍然适用: extern "abi" fn (例如,extern "system" fn

Structs

Enums