前言#
昨天我们写了一个简单的猜数游戏,知道了如何引入依赖以及rust垃圾处理机制相关的基础知识。今天我们来优化下昨天写的代码。如果没看过上篇文章可以看下,当然,点个赞那就更好了
正文#
旧代码#
use rand::Rng;
use std::{io, cmp::Ordering};
fn main() {
let mut rand = rand::thread_rng().gen_range(1..=100);
println!("the rand num is {rand}");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("something error");
let guess: u32 = guess.trim().parse().expect("parse error");
println!("your input is {guess}");
match guess.cmp(&mut rand) {
Ordering::Greater => println!("is larger"),
Ordering::Less => println!("is smaller"),
Ordering::Equal => {
println!("you win!");
}
}
}遗留问题--变量隐藏(shadow)#
let guess: u32 = guess.trim().parse().expect("parse error");这行代码有一个点我们昨天没有分析,那就是guess变量在这之前已经声明过了,而这里又重新声明了一次,但是运行完全没问题。
这又涉及到了rust另一个概念shadow,翻译过来叫隐藏,我更喜欢叫覆盖。后面的同名变量会覆盖掉前面的同名变量,旧的会被释放。 为什么要这么设计呢?我们来看个demo
let mut str: String = String::from("test");
let mut str_num: i32 = str.len();我们生产了一个字符串,然后将这个字符串的长度赋值给str_num。这在rust中是没问题的,可正常运行。但也可以改为
let mut str: String = String::from("test");
let mut str: i32 = str.len();这样也是没问题的,这么做可以节省一个命名空间,少费点脑力~doge。不过这个例子不是很好,正常情况还是分开比较好,shadow可以用在当你确定不再需要某个变量时,这时就可以覆盖了。
优化1--结果正确时再退出程序#
我们目前的代码是每次猜数比较后都退出程序,然后都需要手动去cargo run才能再重新输入。正常的项目都不会让用户每次都去手动触发,我们需要调整为等待正确时再退出程序。
那么要怎么做才能做到等待呢?其实我们完全可以基于循环去做,在stdin输入流读取行的时候是会阻塞程序往下执行的,所以并不用担心循环会执行非常多次。
rust中循环的用法和其它语言差不多,有while[1]、for[2]以及loop[3]。
我们给我们的代码加上loop,直到cmp为Equal时结束loop。
use rand::Rng;
use std::{cmp::Ordering, io};
fn main() {
loop {
let mut rand = rand::thread_rng().gen_range(1..=100);
println!("the rand num is {rand}");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("something error");
let guess: u32 = guess.trim().parse().expect("parse error");
println!("your input is {guess}");
match guess.cmp(&mut rand) {
Ordering::Greater => println!("is larger"),
Ordering::Less => println!("is smaller"),
Ordering::Equal => {
println!("you win!");
break;
}
}
}
}这里有两个改动点
- 外层加了
loop包裹 Equal的作用域中加了break;语句。
重新cargo run一下,发现输入值大或者小都不会退出程序。

不过这里还有一个问题,请往下看。
优化2--parse异常跳过处理#
上面的代码优化后看似没问题了,但其实遇到错误时还是会当场崩溃跳出程序,具体是哪里?往下看。
let guess: u32 = guess.trim().parse().expect("parse error");这行代码我们先是调用String类型的trim方法去头擦尾,然后又调用parse方法将它处理成u32类型,这个u32暂时可以理解为正整数类型,32位的。如果想要支持负数则可以改为i32类型。
这里其实还有一个问题,那就是parse怎么知道我们是想要整数的?难不成会自动判断?并不会,其实parse转换成什么取决于你给guess变量重新声明时赋予的类型,这里为u32自然就是正整数。
如果我们给它改成bool类型,再重新输入数字时就会报错

输出的提示语则是我们expect出去的msg:parse error。
我们并没有办法去控制用户的想法,让他一定要输入数字,所以我们需要对异常场景做处理以保证程序的正确运行。
要怎么做呢?
parse方法返回的是一个Result这个mod的Result枚举,.expect则是Result这个mod里的方法。 而既然是枚举,那就可以用来匹配,还记得我们昨天说的match吗?在这里完全可以用。
修改下我们的代码
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("parse error");
continue;
},
};Result的这个枚举中包含一个Ok和一个Err的enum struct[4],也就是一个枚举实例。虽然是枚举,但是在rust中是允许传入数据的,比如这个Ok中的这个num代表着Ok这个枚举类型的枚举值。这个点我们后面会学到,就暂时这么想。
然后我们重新cargo run

异常的情况被我们continue绕过了。
最终代码#
use rand::Rng;
use std::{cmp::Ordering, io};
fn main() {
loop {
let mut rand = rand::thread_rng().gen_range(1..=100);
println!("the rand num is {rand}");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("something error");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("parse error");
continue;
},
};
println!("your input is {guess}");
match guess.cmp(&mut rand) {
Ordering::Greater => println!("is larger"),
Ordering::Less => println!("is smaller"),
Ordering::Equal => {
println!("you win!");
break;
}
}
}
}
今天就先学到这吧。。。周末啥都不想干。。。
最后如果觉得对你有用的话麻烦点个赞,谢谢~
参考#
- ^rust-while https://doc.rust-lang.org/stable/book/ch03-05-control-flow.html?search=for
- ^rust-for https://doc.rust-lang.org/stable/book/ch03-05-control-flow.html?search=for
- ^rust-loop https://doc.rust-lang.org/stable/book/ch03-05-control-flow.html?highlight=loop#repetition-with-loops
- ^rust-enum https://doc.rust-lang.org/stable/book/ch06-01-defining-an-enum.html?highlight=enum#defining-an-enum
发布于 2022-11-26 21:54・IP 属地广东
