本文发表于入职啦(公众号: ruzhila) 大家可以访问入职啦学习更多的编程实战。
今天在Rust学习群中看到一个提问:
这是一个入门的猜数程序,通过输入数字来猜测一个随机数,如果猜对了就结束程序,如果猜错了就继续猜,直到猜对为止。
问题分析
从代码来看,程序发生了崩溃,很多rust初学者在学习的过程中,会遇到第一个经典错误:
io::stdin().read_line(buf:&mut num).expect(msg:"read Err");
num.parse().expect("u32 Err");
因为控制台输入回车的时候,意味这提交了一个换行符,这个换行符会被read_line读取到num中,比如你输入1,实际上num中的值是1\n,这个时候parse就会报错,因为parse无法解析这个换行符。
一开始我以为是这个错误,刚想说,这个错误太简单了,改成:
- num.parse().expect("u32 Err");
+ num.trim().parse().expect("u32 Err");
把\n去掉就行了,刚想发出去,群里有人说:“是不是变量遮蔽” ?仔细一看,好像不是那么一回事,因为提问的代码中已经处理了.trim()
为了方便理解,我把代码贴出来:
use std::io;
use rand::Rng;
use std::cmp::Ordering;
fn main(){
println!("Welcome guessing number!")
println!("请输入一个数")
let mut num:String = String::new();
let rand_num: i32 = rand::thread_rng().gen_range(low:1 ,high:11);
println!("这个随机数字是:{}",rand_num);
loop {
io::stdin().read_line(buf:&mut num).expect(msg:"read Err");
println!("你输入的数字是{}",num);
let num:i32=num.trim().parse().expect(msg:"u32 Err");
match num.cmp(&rand num){
Ordering::Equal => print!("nihao")
Ordering::Less=> print!("little")
Ordering::Greater=>print!("great")
}
}
}
在代码的第9行,定义了一个String类型的num, 然后在15行重新定义了i32的num, 提问的人也说是第二次输入才会出现crash。 那么是不是因为变量遮蔽导致的问题呢?
变量遮蔽
在rust中,可以通过let 关键字重新定义一个变量,这个时候,新定义的变量会遮蔽之前的变量,这个时候,之前的变量就无法访问了,这个时候,如果之前的变量还在使用,就会出现问题。
那么在loop循环中,这种遮蔽规则是怎么样的呢? 其实并不会,因为rust是静态类型语言,同样的代码在python, js中确实是第二次循环的时候,num就变成了i32了:
num = "1"
for i in range(2):
assert type(num) == str
num = int(num)
很不幸,第二次运行的时候, num的类型就不是str了
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
AssertionError
回来看rust的代码,第二次循环的时候,在第13行的时候,num仍然还是String类型, 并不会因为15行的重新定义而改变类型,所以这个时候,代码并不会出现问题。
因为在rust中,变量的作用域是严格被约束的,i32的生命周期在17-22行之间,在9-16之间的生命周期还是属于String类型的num。
loop的每次循环,其实和第一遍的代码是完全一样的行为,因为静态语言和动态语言并不一样,并不会在运行时改变变量的类型。
可以理解为,rust是一种静态类型语言,变量的类型是在编译时确定的,而不是在运行时确定的。
真正的问题是什么?
其实群里就有人第一时间指出了问题,是因为read_line的append行为导致的,通过查看read_line的文档,我们可以看到read_line的定义是这样的:
/// Locks this handle and reads a line of input, appending it to the specified buffer.
pub fn read_line(&self, buf: &mut String) -> io::Result<usize> {
...
之所以在输入第二次的时候出现crash,是因为在代码的第15行,read_line会把输入的内容追加到num中,这个时候,num的内容就变成了1\n2\n,虽然调用了trim.(),但是trim只会去掉首尾的空格,而不会去掉中间的换行符,所以parse的时候就会出现问题。
解决这个问题的方法很简单,改变一下num的声明顺序保证每次 num 都是空的。 将第9行的代码改到第14行就可以了。
use std::io;
use rand::Rng;
use std::cmp::Ordering;
fn main(){
println!("Welcome guessing number!"))
println!("请输入一个数")
let rand_num: i32 = rand::thread_rng().gen_range(low:1 ,high:11);
println!("这个随机数字是:{}",rand_num);
loop {
let mut num:String = String::new();
io::stdin().read_line(buf:&mut num).expect(msg:"read Err");
println!("你输入的数字是{}",num);
let num:i32=num.trim().parse().expect(msg:"u32 Err");
match num.cmp(&rand num){
Ordering::Equal => print!("nihao")
Ordering::Less=> print!("little")
Ordering::Greater=>print!("great")
}
}
}
访问官网 https://www.ruzhila.cn/blog/?from=rust 获取更多学习资源
对编程感兴趣的同学可以加入我们的学习群,一起学习,一起进步