< 返回版块

shenjinti 发表于 2024-03-23 14:30

今天在Rust学习群中看到一个提问: crash

这是一个入门的猜数程序,通过输入数字来猜测一个随机数,如果猜对了就结束程序,如果猜错了就继续猜,直到猜对为止。

问题分析

从代码来看,程序发生了崩溃,很多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")
        }
    }
}

在代码的第8行,定义了一个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的代码,第二次循环的时候,在第15行的时候,num仍然还是String类型, 并不会因为17行的重新定义而改变类型,所以这个时候,代码并不会出现问题。

因为在rust中,变量的作用域是严格被约束的,i32的生命周期在17-22行之间,在8-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 都是空的。 将第8行的代码改到第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")
        }
    }
}

欢迎大家关注添加我的微信交流学习编程: jinti2000, 也可以关注公众号 入职啦(ruzhila)

访问官网 https://www.ruzhila.cn 获取更多学习资源


Ext Link: https://ruzhila.cn/?from=rustcc

评论区

写评论
asuper 2024-03-25 16:48

和生命周期没关系,你可以说是学习Stdin::read_line或String的用法。。。

ankoGo 2024-03-24 18:03

当然这个跟“Rust变量生命周期”也没啥关系,因为其他语言也会出现,你懂的,只要你这样去写代码,都会遇到这个问题

ankoGo 2024-03-24 18:02

一般这个问题只会出现在新手时候,老手一般不会出现这样的代码!肯定会直接就写对了!根本不会给bug代码留下机会

1 共 3 条评论, 1 页