知识点1:match 穷举匹配
match 是 Rust 最强大的控制流工具,必须穷举所有可能的情况:
fndescribe_number(n:i32)->String{matchn{0=>String::from("零"),1..=9=>String::from("个位数"),// 1到910..=99=>String::from("两位数"),// 10到99100..=999=>String::from("三位数"),// 100到999_=>String::from("很大的数"),// _ 匹配所有其他情况}}fnmain(){println!("{}",describe_number(0));// 零println!("{}",describe_number(7));// 个位数println!("{}",describe_number(42));// 两位数println!("{}",describe_number(500));// 三位数println!("{}",describe_number(1000));// 很大的数}知识点2:match 解构枚举和结构体
enumCommand{Echo(String),Move{x:i32,y:i32},Quit,}structPoint{x:i32,y:i32,}fnmain(){// 匹配枚举letcmd=Command::Move{x:10,y:20};matchcmd{Command::Echo(msg)=>println!("回声: {}",msg),Command::Move{x,y}=>println!("移动到 ({}, {})",x,y),Command::Quit=>println!("退出"),}// 匹配结构体letp=Point{x:3,y:7};matchp{Point{x:0,y}=>println!("在Y轴上, y={}",y),Point{x,y:0}=>println!("在X轴上, x={}",x),Point{x,y}=>println!("普通点 ({}, {})",x,y),}// 匹配元组letpair=(1,2);matchpair{(0,_)=>println!("第一个是0"),(_,0)=>println!("第二个是0"),(x,y)=>println!("两个数: {} 和 {}",x,y),}}知识点3:匹配守卫(Match Guard)
在模式后面加 if 条件,进一步过滤:
fnclassify(n:i32)->&'staticstr{matchn{xifx<0=>"负数",0=>"零",xifx>0&&x<=100=>"小的正数",xifx>100=>"大的正数",_=>"其他",}}fnmain(){println!("{}",classify(-5));// 负数println!("{}",classify(0));// 零println!("{}",classify(42));// 小的正数println!("{}",classify(200));// 大的正数}知识点4:if let 和 while let
当你只关心一种匹配情况时,用 if let 更简洁:
fnmain(){letvalue:Option<i32>=Some(42);// 用 match(啰嗦)matchvalue{Some(v)=>println!("值是 {}",v),_=>{}}// 用 if let(简洁)ifletSome(v)=value{println!("值是 {}",v);}// if let 也可以加 elseletempty:Option<i32>=None;ifletSome(v)=empty{println!("值是 {}",v);}else{println!("没有值");}// while let:循环直到匹配失败letmutstack=vec![1,2,3,4,5];whileletSome(top)=stack.pop(){println!("弹出: {}",top);}// 输出: 5, 4, 3, 2, 1(逐个弹出直到栈空)}知识点5:let else 和 @ 绑定
fnmain(){// let else:匹配失败时执行 else 分支(必须 diverge:return/break/panic)letvalue:Option<i32>=Some(42);letSome(v)=valueelse{println!("没有值,提前返回");return;};println!("拿到值: {}",v);// 42// @ 绑定:匹配的同时把整个值绑定到一个变量letmsg=String::from("hello");matchmsg.len(){n@1..=5=>println!("短字符串,长度={}",n),n@6..=10=>println!("中等字符串,长度={}",n),n=>println!("长字符串,长度={}",n),}// @ 绑定在枚举中letvalue:Option<i32>=Some(42);matchvalue{Some(n@1..=100)=>println!("1到100之间的数: {}",n),Some(n)=>println!("其他数: {}",n),None=>println!("没有值"),}}核心规则
概念 写法
穷举匹配 match value { pattern => expr, … }
通配符 _ => …
范围匹配 1…=9
匹配守卫 x if x > 0 => …
单分支匹配 if let Pattern = value { … }
循环匹配 while let Pattern = value { … }
匹配失败分支 let Pattern = value else { … };
绑定匹配值 n @ pattern => …
解构结构体 Struct { field1, field2 }
解构枚举 Enum::Variant(data)
动手试试
补全下面的代码:
enumExpr{Num(f64),Add(Box<Expr>,Box<Expr>),Sub(Box<Expr>,Box<Expr>),Mul(Box<Expr>,Box<Expr>),Div(Box<Expr>,Box<Expr>),}// 补全:实现 eval 函数,递归计算表达式的值// 除法遇到除数为0时返回 Nonefneval(expr:&Expr)->Option<f64>{// 补全这里// 提示:用 match 匹配每种情况,递归调用 eval// Add: a + b// Sub: a - b// Mul: a * b// Div: a / b(b为0返回None)// 用 if let 或 match 处理递归结果中可能出现 None 的情况todo!()}// 补全:实现 describe 函数,返回表达式的字符串描述// 例如: Add(Num(1), Num(2)) => "(1 + 2)"// Mul(Num(3), Add(Num(4), Num(5))) => "(3 * (4 + 5))"fndescribe(expr:&Expr)->String{// 补全这里todo!()}fnmain(){// 表达式: (3 + 4) * 2letexpr=Expr::Mul(Box::new(Expr::Add(Box::new(Expr::Num(3.0)),Box::new(Expr::Num(4.0)),)),Box::new(Expr::Num(2.0)),);// 补全:打印表达式描述和计算结果// 期望输出:// 表达式: ((3 + 4) * 2)// 结果: Some(14)// 表达式: 10 / (5 - 5)letdiv_zero=Expr::Div(Box::new(Expr::Num(10.0)),Box::new(Expr::Sub(Box::new(Expr::Num(5.0)),Box::new(Expr::Num(5.0)),)),);// 补全:打印表达式描述和计算结果// 期望输出:// 表达式: (10 / (5 - 5))// 结果: None}期望输出:
表达式: ((3 + 4) * 2)
结果: Some(14)
表达式: (10 / (5 - 5))
结果: None
写好了贴过来,没问题就进入第15课:泛型 Generics 👋
贴代码
下面给出完整可运行代码, eval 和 describe 全部补全,并附带讲解。
enumExpr{Num(f64),Add(Box<Expr>,Box<Expr>),Sub(Box<Expr>,Box<Expr>),Mul(Box<Expr>,Box<Expr>),Div(Box<Expr>,Box<Expr>),}// 递归计算表达式,除数为0返回None,子表达式出错也返回Nonefneval(expr:&Expr)->Option<f64>{matchexpr{Expr::Num(n)=>Some(*n),Expr::Add(a,b)=>{letva=eval(a)?;letvb=eval(b)?;Some(va+vb)}Expr::Sub(a,b)=>{letva=eval(a)?;letvb=eval(b)?;Some(va-vb)}Expr::Mul(a,b)=>{letva=eval(a)?;letvb=eval(b)?;Some(va*vb)}Expr::Div(a,b)=>{letva=eval(a)?;letvb=eval(b)?;ifvb==0.0{None}else{Some(va/vb)}}}}// 生成带括号的表达式字符串fndescribe(expr:&Expr)->String{matchexpr{Expr::Num(n)=>format!("{}",n),Expr::Add(a,b)=>format!("({} + {})",describe(a),describe(b)),Expr::Sub(a,b)=>format!("({} - {})",describe(a),describe(b)),Expr::Mul(a,b)=>format!("({} * {})",describe(a),describe(b)),Expr::Div(a,b)=>format!("({} / {})",describe(a),describe(b)),}}fnmain(){// 表达式: (3 + 4) * 2letexpr=Expr::Mul(Box::new(Expr::Add(Box::new(Expr::Num(3.0)),Box::new(Expr::Num(4.0)),)),Box::new(Expr::Num(2.0)),);println!("表达式: {}",describe(&expr));println!("结果: {:?}",eval(&expr));// 表达式: 10 / (5 - 5)letdiv_zero=Expr::Div(Box::new(Expr::Num(10.0)),Box::new(Expr::Sub(Box::new(Expr::Num(5.0)),Box::new(Expr::Num(5.0)),)),);println!("表达式: {}",describe(&div_zero));println!("结果: {:?}",eval(&div_zero));}运行输出
plaintext
表达式: ((3 + 4) * 2)
结果: Some(14.0)
表达式: (10 / (5 - 5))
结果: None
重点知识点拆解
- 递归枚举 + Box
Add(Box<Expr>,Box<Expr>)Expr 递归包含自身,Rust 无法在栈上直接存放无限大小的递归类型,必须用 Box(堆指针)。
Box :把 T 放到堆,栈上只存一个指针,解决递归类型大小不确定问题。
- ? 运算符(问号)
letva=eval(a)?;eval(a)返回Option<f64>。- 如果是 Some(v) → 把 v 赋值给 va;
- 如果是 None → 整个 eval 函数立刻提前返回 None。
? 是错误传播语法糖,等价于:
letva=matcheval(a){Some(v)=>v,None=>returnNone,};⚠️ ? 只能用在返回 Option / Result 的函数内部,main 里面不能直接写。
- describe 递归打印
每一个二元运算符都包一层括号,不管运算符优先级,简单粗暴,保证结构清晰:
Add(Num(3),Num(4))→(3+4)拓展思考(进阶)
当前方案不做运算符优先级,所以 (3 + 4) * 2 输出 ((3 + 4) * 2) ,是正确的;
但如果表达式是 Add(Mul(Num(3),Num(4)),Num(5)) ,现在输出 ((3 * 4) + 5) ,
数学上其实可以省去外层括号 (3 * 4) + 5 。
想实现智能括号(根据优先级决定要不要加括号),就是一道经典的中级Rust练习题。