if let 语法允许你将 if 和 let 结合起来,以一种不那么冗长的方式来处理匹配一个模式的值,而忽略其余的值。考虑一下示例 6-6 中的程序,该程序匹配 config_max 变量中的 Option<u8> 值,但只想在值为 Some 变体时执行代码。
fnmain() {
let config_max = Some(3u8);
match config_max {
Some(max) => println!("The maximum is configured to be {max}"),
_ => (),
}
}
示例 6-6: 一个 match,它只关心在值为 Some 时执行代码
如果值是 Some,我们通过将值绑定到模式中的变量 max 来打印 Some 变体中的值。我们不想对 None 值做任何事情。为了满足 match 表达式,我们必须在仅处理一个变体后添加 _ => (),这是一个令人恼火的样板代码。
相反,我们可以使用 if let 以更简洁的方式编写此代码。以下代码的行为与示例 6-6 中的 match 相同
fnmain() {
let config_max = Some(3u8);
ifletSome(max) = config_max {
println!("The maximum is configured to be {max}");
}
}
if let 语法接受一个模式和一个表达式,它们之间用等号分隔。它的工作方式与 match 相同,表达式被赋予 match,而模式是它的第一个分支。在这种情况下,模式是 Some(max),而 max 绑定到 Some 内部的值。然后我们可以在 if let 代码块的主体中使用 max,就像我们在相应的 match 分支中使用 max 一样。只有当值与模式匹配时,if let 代码块中的代码才会运行。
使用 if let 意味着更少的输入,更少的缩进和更少的样板代码。但是,你会失去 match 强制执行的穷尽性检查。在 match 和 if let 之间进行选择取决于你在特定情况下正在做什么,以及为了获得简洁性而牺牲穷尽性检查是否是适当的权衡。
换句话说,你可以将 if let 视为 match 的语法糖,当值匹配一个模式时运行代码,然后忽略所有其他值。
我们可以在 if let 中包含一个 else。与 else 一起使用的代码块与 match 表达式中与 _ 情况一起使用的代码块相同,该表达式等效于 if let 和 else。回想一下示例 6-4 中的 Coin 枚举定义,其中 Quarter 变体也包含一个 UsState 值。如果我们想计算我们看到的所有非四分之一美元硬币,同时宣布四分之一美元硬币的状态,我们可以使用 match 表达式来做到这一点,就像这样
#[derive(Debug)]// so we can inspect the state in a minuteenumUsState {
Alabama,
Alaska,
// --snip--}
impl UsState {
fnexisted_in(&self, year: u16) -> bool {
matchself {
UsState::Alabama => year >= 1819,
UsState::Alaska => year >= 1959,
// -- snip --
}
}
}
enumCoin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fndescribe_state_quarter(coin: Coin) -> Option<String> {
iflet Coin::Quarter(state) = coin {
if state.existed_in(1900) {
Some(format!("{state:?} is pretty old, for America!"))
} else {
Some(format!("{state:?} is relatively new."))
}
} else {
None }
}
fnmain() {
ifletSome(desc) = describe_state_quarter(Coin::Quarter(UsState::Alaska)) {
println!("{desc}");
}
}
然后我们可能会使用 if let 来匹配硬币的类型,在条件的主体中引入一个 state 变量,如示例 6-7 所示。
文件名: src/main.rs
#[derive(Debug)]// so we can inspect the state in a minuteenumUsState {
Alabama,
Alaska,
// --snip--}
impl UsState {
fnexisted_in(&self, year: u16) -> bool {
matchself {
UsState::Alabama => year >= 1819,
UsState::Alaska => year >= 1959,
// -- snip -- }
}
}
enumCoin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fndescribe_state_quarter(coin: Coin) -> Option<String> {
iflet Coin::Quarter(state) = coin {
if state.existed_in(1900) {
Some(format!("{state:?} is pretty old, for America!"))
} else {
Some(format!("{state:?} is relatively new."))
}
} else {
None
}
}
fnmain() {
ifletSome(desc) = describe_state_quarter(Coin::Quarter(UsState::Alaska)) {
println!("{desc}");
}
}
示例 6-7: 使用
这完成了工作,但它将工作推入了 if let 语句的主体中,如果要做的工作更复杂,则可能很难准确地跟踪顶级分支之间的关系。我们还可以利用表达式产生值的特性,要么从 if let 产生 state,要么提前返回,如示例 6-8 所示。(当然,你也可以使用 match 做类似的事情!)
文件名: src/main.rs
#[derive(Debug)]// so we can inspect the state in a minuteenumUsState {
Alabama,
Alaska,
// --snip--}
impl UsState {
fnexisted_in(&self, year: u16) -> bool {
matchself {
UsState::Alabama => year >= 1819,
UsState::Alaska => year >= 1959,
// -- snip -- }
}
}
enumCoin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fndescribe_state_quarter(coin: Coin) -> Option<String> {
let state = iflet Coin::Quarter(state) = coin {
state
} else {
returnNone;
};
if state.existed_in(1900) {
Some(format!("{state:?} is pretty old, for America!"))
} else {
Some(format!("{state:?} is relatively new."))
}
}
fnmain() {
ifletSome(desc) = describe_state_quarter(Coin::Quarter(UsState::Alaska)) {
println!("{desc}");
}
}
示例 6-8: 使用 if let 生成一个值或提前返回。
但这在某种程度上也很难理解!if let 的一个分支产生一个值,而另一个分支完全从函数返回。
为了使这种常见的模式更易于表达,Rust 具有 let-else。let-else 语法在左侧接受一个模式,在右侧接受一个表达式,与 if let 非常相似,但它没有 if 分支,只有 else 分支。如果模式匹配,它将在外部作用域中绑定来自模式的值。如果模式不匹配,程序将流入 else 分支,该分支必须从函数返回。
在示例 6-9 中,你可以看到示例 6-8 在使用 let-else 代替 if let 时的样子。请注意,它以这种方式保持在函数主体中的 “happy path” 上,而不会像 if let 那样对两个分支具有显着不同的控制流。
文件名: src/main.rs
#[derive(Debug)]// so we can inspect the state in a minuteenumUsState {
Alabama,
Alaska,
// --snip--}
impl UsState {
fnexisted_in(&self, year: u16) -> bool {
matchself {
UsState::Alabama => year >= 1819,
UsState::Alaska => year >= 1959,
// -- snip -- }
}
}
enumCoin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fndescribe_state_quarter(coin: Coin) -> Option<String> {
let Coin::Quarter(state) = coin else {
returnNone;
};
if state.existed_in(1900) {
Some(format!("{state:?} is pretty old, for America!"))
} else {
Some(format!("{state:?} is relatively new."))
}
}
fnmain() {
ifletSome(desc) = describe_state_quarter(Coin::Quarter(UsState::Alaska)) {
println!("{desc}");
}
}
示例 6-9: 使用 let-else 来阐明函数中的流程。
如果你遇到程序逻辑过于冗长而无法使用 match 表达的情况,请记住 if let 和 let else 也在你的 Rust 工具箱中。