rust-简洁的控制流

简洁的控制流:if let 和 let else

if let 语法让你可以将 if 和 let 结合起来,以更简洁的方式处理匹配某一模式的值,同时忽略其他情况。考虑清单6-6中的程序,它对 config_max 变量中的 Option<u8> 值进行匹配,但只在值是 Some 变体时执行代码。

let config_max = Some(3u8);
match config_max {
    Some(max) => println!("The maximum is configured to be {max}"),
    _ => (),
}

清单6-6:仅在值为 Some 时执行代码的 match
如果值是 Some,我们通过模式绑定将其中的值赋给变量 max 并打印出来。对于 None 值我们不做任何操作。为了满足 match 表达式,我们必须在处理完一个变体后添加 _ => (),这是一段令人烦恼的样板代码。

相反,我们可以用 if let 写得更短一些。下面这段代码与清单6-6中的 match 行为相同:

let config_max = Some(3u8);
if let Some(max) = config_max {
    println!("The maximum is configured to be {max}");
}

if let 的语法由一个模式和一个表达式组成,中间用等号分隔。它工作原理类似于 match,其中表达式作为被匹配对象,模式则是第一个分支。在这里,模式是 Some(max),max 会绑定到 Some 中包含的值,然后我们就可以像使用对应 match 分支里的 max 一样,在 if let 块中使用它。只有当值匹配该模式时,if let 块内的代码才会运行。

使用 if let 意味着输入更少、缩进更浅、样板代码也减少了。但这样做会失去 match 所强制执行的穷尽检查。在具体场景下选择使用 match 或 if let,要看是否愿意以牺牲穷尽性检查换取简洁性。

换句话说,可以把 if let 看作一种语法糖,用来写只针对某个特定模式运行代码,而忽略所有其他情况的 match。

我们还可以给 if let 加上 else 分支。这部分与等价于该 if let…else 的 match 表达式中 _ 情况对应。例如回想清单6-4中定义了 Coin 枚举,其中 Quarter 变体还携带了 UsState 值。如果想统计非 quarter 硬币数量,同时报告 quarter 来自哪个州,可以用如下match:

let mut count = 0;
match coin {
    Coin::Quarter(state) => println!("State quarter from {state:?}!"),
    _ => count += 1,
}

或者用 if let 和 else 实现:

let mut count = 0;
if let Coin::Quarter(state) = coin {
    println!("State quarter from {state:?}!");
} else {
    count += 1;
}

保持“顺畅路径”——let…else

常见的一种编程习惯是在有有效值时进行计算,否则返回默认结果。继续之前关于含 UsState 的硬币例子,如果想根据季度硬币所属州存在多久来说点幽默的话,可以给 UsState 添加方法判断某年该州是否已存在,如下:

impl UsState {
    fn existed_in(&self, year: u16) -> bool {
        match self {
            UsState::Alabama => year >= 1819,
            UsState::Alaska => year >= 1959,
            // -- 略 --
        }
    }
}

然后利用 if let 匹配硬币类型,并在条件块内引入 state,如清单6-7所示。

文件名:src/main.rs

fn describe_state_quarter(coin: Coin) -> Option<String> {
    if let 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
    }
}

清单6-7:通过嵌套条件判断状态是否存在于1900年
这种写法能完成任务,但把逻辑都塞进了 if-let 内部,如果逻辑复杂可能难以理解顶层分支之间关系。同样,也可利用表达式产生返回值这一特点,通过早期返回实现,如清单6-8。(match 同理)

文件名:src/main.rs

fn describe_state_quarter(coin: Coin) -> Option<String> {
    // 如果不是 Quarter,则提前返回 None;否则提取出 state。
	let state = if let Coin::Quarter(state) = coin { 
		state 
	} else { 
		return None; 
	};

	if state.existed_in(1900){
	    Some(format!("{state:?} is pretty old, for America!"))
	}else{
	    Some(format!("{state:?} is relatively new."))
	}
}

清单6-8:利用if-let生成value或提前return
不过,这种写法本身也稍显繁琐!因为一边产生 value,一边直接从函数返回,两条分支控制流差异较大。

为了让这种常见写法更加优雅,Rust 引入了 let...else 。其语法左侧为模式,右侧为表达式,与 if-let 类似,但没有独立 “if” 分支,仅有 “else”。若匹配成功,会将绑定变量放置外层作用域;若失败,则进入必须从函数退出(如 return)的 else 分支。

看一下如何用 let...else 改写前面例子(即替代 Listing 6-8),请参见清单6-9。这种方式使主流程保持“顺畅路径”,避免因两条不同控制流而导致混乱:

文件名:src/main.rs

fn describe_state_quarter(coin: Coin) -> Option<String> {

	let Coin::Quarter(state)=coin else{
	    return None;
	};

	if state.existed_in(1900){
	    Some(format!("{state:?} is pretty old, for America!"))
	}else{
	    Some(format!("{state:?} is relatively new."))
	}

}

清单6-9:使用let…else澄清函数流程

如果遇到需要比match 更冗长才能描述逻辑的问题,不妨记住 Rust 工具箱里还有 if-letlet...else 可供选择。

总结

至此,我们介绍了如何借助枚举创建可选多态类型,以及标准库提供帮助防止错误发生的 Option 类型。当枚举携带数据时,可根据需求选择使用match或if-let解构并访问这些数据,从而灵活应对各种情况。

你的 Rust 程序现在能够通过结构体和枚举准确地表示领域概念,为 API 创建自定义类型确保类型安全——编译器保证每个函数只能接收预期的数据类型参数。

为了向用户提供组织良好且易于使用,只暴露必要内容的软件接口,让我们接下来转向学习 Rust 模块系统吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值