Rust 学习 01
archive time: 2021-06-05
今天开始在博客记录自己学习Rust的笔记
对于Rust
这门语言及其特点,相信大家或多或少都听说过,比如拥有 最智能的编译器,良好的包管理器 什么的,还有 函数式编程,这也是其一大特色
然而我为什么要学Rust
呢?纯粹是因为我喜欢他的图标,没错,就是那只大螃蟹;其次就是他的编译器检查,真的是太智能了,能够精确定位到是哪一行代码出现了问题,并且能给出一定的修改提示
我这个博客,很明显,是一个系列,我会依照The Rust Programming Language来学习
Rust 的安装什么的我就跳过了,太基础了
猜数游戏
由于本系列的博客是类似与总结一样的存在,所以这里我不会按照书上的顺序一步一步来教,而是直接讲最后的代码,并且将要点总结一下,仅此而已
所以,如果真的是一点基础也没有的话,建议还是看书来学,这样比较稳当
最终的代码
下面展示的,就是这章过后你写出的代码
use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..101);
loop {
println!("Please input your guess.");
let mut guess = String::new();
io::stdin().read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
println!("You guessed: {}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}
看起来还是很长的,但每个部分的功能还是很清晰的
库 (crate) 以及 Cargo
首先是头部那一堆use
这个就是Rust
中导包的语句,使用某个库,类比于Java
中的import
,和C
或Cpp
的#include
还是不太一样的1,但也可以类比
导入的库分别是
std
,即 standard,也就是标准库的意思,说明前两个使用都是导入标准库中的io
和cmp
,也就是输入输出模块 (Module) 及比较 (compare) 模块
其中,std::cmp::Ordering
导入的其实是cmp
这个模块中的一个Enums
,一个枚举类型
有关类型的事情,后面的章节会详细讲解,这里就不仔细说了,大致就是有很多个选项的这样一个类型,可以类比C
语言中的枚举类型
之后,rand
,这是一个外部库,而Rng
则是这个库中的一个特征 (Trait),类比其他编程语言接口
在Rust
的标准库里,没有能够生成 伪随机数 的库,所以这样需要用到rand
这样一个库来为我们生成一个随机数,也就是我们要猜的那个数
要使用rand
这个库,我们只需要在Cargo.toml
里写入
[dependencies]
rand = "0.8"
就可以了,cargo
在编译的时候会自动下载0,7
这个版本号中最新的版本,目前最新版是0.8.3
关于cargo
的使用,初学者只需要记住
cargo new <project-name> # 创建工程
cargo build # 编译项目
cargo run # 运行项目
这几个比较基础的命令就可以了
之后,fn main()
一句标志着程序的入口,和其他语言是一样的
println!()
这里要注意了,这个并不是个函数 (method, funtuon),而是一个宏 (Macro),作用是输出内容到屏幕上
至于宏和函数的区别,之后会提到,现在只需要记住,宏的名称后面会有一个!
,而函数没有
变量声明
let secret_number
一句,首先,let
用来声明了一个“变量“,为什么要打引号呢?因为Rust
中的变量 (Variable) 默认是 immutable
的,不可变的,如果要定义一个可变变量,则需要使用let mut <var-name>
的形式
同时注意到这里并没有声明变量的类型,而Rust
作为一门强类型的静态语言,每个变量都会有确定的类型
这是因为Rust
的编译器 足够智能,可以自动进行类型推断,而不需要手动声明类型,但是如果要声明类型也是可以的,可以采用下面这种方式
let a: i32;
let <var-name>: <type>
这样的方式就是声明类型了,具体RSust
中有哪些类型可以参考std
库中的描写
rand::thread_rng::gen_range(1..101)
,这句调用了定义在rand
库 (crate) 的函数thread_rng
,生成了一个rand::rngs::ThreadRng
类型,定义在这个类型上有一个从rang::Rng
中实现的方法gen_range
,也就是在一个范围内生成随机数,当然,这个随机数是整形的,参数格式是
gen_range(<lower>..<upper>) // [lower, upper)
gen_range(<lower>..=<upper>) // [lower, upper]
也就是一个是不包括右边界的,一个是包括右边界的,所以这行代码还可以写成
let secret_number = rand::thread_rng().gen_range(1..=100); // 在[1, 100]生成随机数
之后的loop { ... }
实现了一个无限循环,在花括号内的所有代码都会无限制循环,除非使用break;
跳出
let mut guess = String::new();
io::stdin().read_line(&mut guess)
.expect("Failed to read line");
这两句连在一起看,首先是定义了一个可变变量,叫guess
,为他 绑定 (bind) 了一个值
为什么强调是绑定,而不是说赋值呢?这就要涉及到Rust
的变量机制了,之后会提到,感兴趣的可以翻阅标准库的资料2查看
绑定值的就是String::new()
的结果,也就是String
类型的一个对象,String
类型在Rust
中可以看作就是一个可变长度的字符串数组,支持utf-8
编码
io::stdin().read_line(&mut guess)
,这句就是从标准输入流 (stdin) 中读取一行输入,并将读取到的内容赋给了guess
为什么要用&mut guess
呢?我们要对guess
的值进行操作,所以应该传入一个 引用,学过C++
的应该对引用这个比较清楚了,所以这里应该传入一个&guess
但是为什么是&mut
呢?这是因为Rust
中,引用默认也是不可变的,而我们这里就是要改变引用的值,故需要使用mut
来修饰
.expect()
一句是对read_line()
的返回值进行的一个操作,read_line()
的返回值是一个Result
类型的枚举,有Ok
和Err
两种状态,也就是读取成功和读取失败,expect
说明了如果读取失败返回的值,而读取成功则不会执行
shadow 以及 match
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
这一句里有两个点要注意,一个是变量之间的shadow
,另一个就是match
语句
这里可以看到,我们又一次地声明了guess
变量,在其他静态语言里,这是不允许的,而Rust
可以做到,这是因为Rust
中的shadow
机制
简单来说就是,如果重复声明了一个变量,那么新的变量将会把旧的变量隐藏起来,无法使用,之后出现这个变量名都会指向新的这个变量
这个机制在进行变量类型转换 (cast) 的时候是十分有用的,就比如这里,我们通过parse()
方法将guess
由字符串解析为整形3
parse()
函数的返回值也是一个 Result 类型,在这里,我们需要对这个枚举类型进行选择,即使用match
语句
match
语句的基本格式是
match <var> {
arm1 => doSome1(),
arm2 => doSome2(),
...
}
前面是要 匹配 的 模板,而=>
指向的就是如果模板与我们的变量匹配成功后要进行的操作,这样的一个 模式匹配 的功能还是很强大的,之后的章节会详细讲到
这里我们对Result
类型进行匹配,我们之前说过,Result
类型是一个枚举类型,枚举对象有Ok
,Err
,所以这里我们使用这两个对象来进行匹配,如果解析成功的话,也就是Ok
,那么,我们就将解析到的值保存到一个叫 num 的变量里,之后返回出来,如果失败,那么这次循环就直接continue
跳过了,进行下次循环,再次要求用户输入,直到解析成功,我们才会进行下一步操作
placeholder
println!("You guessed: {}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
这里介绍了Rust
里简单输出变量值的方法,即使用 Placeholder {}
,一对花括号对应一个值,这就有点像Python
的format string
了,但差别还是有的
之后是使用了std::cmp
模块的cmp()
函数,对比两个值,由于要传入值,所以使用了引用的方式,但我们不需要改变值,所以我们没有加上mut
cmp()
的返回值是一个Ordering
类型,一个枚举类型,有Less
,Greater
,Equal
三种结果,这里就是用match
依次比较,如果是Equal
的话,就输出“You win1“,同时跳出循环,至此,程序结束
小结
这个例子是一个十分基础且经典的例子,可以很好的从一个大的方面来看看Rust
的一些特性和基本使用方法,还提到了Cargo
这个优秀的包管理器
这个例子的重点,我认为应该是match
语句的使用以及cargo
的用法还有变量,需要注意
本次Rust
学习笔记到此就结束了,谢谢大家的阅读,希望大家能从中有所收获
C 语言和 C++中的#include
是一个预处理命令,
所做的事情通俗地说就是把你要用的那部分代码直接复制到你那句#include
的位置,而Java
和Rust
却不是这样
找了一下菜鸟教程的一篇教学, 如果不想啃英文的话可以稍微参考一下
我们自己声明guess
为u32
类型,即无符号 32 位整形