本文是Rust学习笔记基础篇,主要包括Rust基础概念、所有权、枚举与模式匹配等相关内容。
环境搭建
学习资料
1 | https://doc.rust-lang.org/book/ |
搭建环境
在wsl上搭建rust的开发环境
1 | curl -L https://raw.githubusercontent.com/rust-lang/rustlings/main/install.sh | bash |
下载rustlings练习
1 | # find out the latest version at https://github.com/rust-lang/rustlings/releases/latest (on edit 5.5.1) |
如果不开代理,可能速度会很慢或者出现下载失败。
git开启代理的方法:
1 | # ~/.ssh/config |
其中4780是科学上网软件的代理端口。
基础编程概念
1 | use std::io; |
- 变量默认不可变,如果要可变,需要添加
mut
关键字修饰。 - 引用默认是不可变的,如果要修改,也需要写成
&mut guess
变量与可变性
1 | fn main() { |
数据类型
Rust是静态类型的语言,需要在编译期就知道所有变量的类型。
标量类型
字符类型
char
,4个字节,表示的是一个Unicode标量值。
复合类型
将多个值组合成一个类型。
元组类型
1 | fn main() { |
元组类型的长度是固定的。
数组类型
数组的元素类型必须是相同的。
1 | fn main() { |
函数
1 | let x = (let y = 6); // 报错 |
因为ley y = 6
这个语句并不返回值。
1 | let y = { |
ok。注意x+1
后面不带分号,它是一个表达式。表达式后面加上分号,就会转换成为语句,而语句不会返回值。
带返回值的函数
1 | fn five() -> i32 { |
控制流
if语句
if 语句的条件必须是 bool 类型。
1 | fn main() { |
if 和 else 需要有相同的值类型。
1 | let number = if condition {5} else {"six"}; // 会报错 |
loop循环
1 | fn main() { |
一般地,break和continue作用域内部的循环,如果希望作用在外部的循环,可以在后面添加loop label。
1 | fn main() { |
带返回值的loop
1 | let mut count = 0; |
带条件的循环
1 | let mut number = 3; |
也可以用for循环:
1 | let a = [1,2,3,4,5]; |
所有权
本节内容:
所有权让Rust无需垃圾回收期即可保证内存安全。理解了Rust的所有权,将能更自然地编写出安全和高效的代码。
什么是所有权
所有权规则
- Rust中的每一个值都有一个被成为其所有者的变量
- 值在任意时刻有且只有一个所有者
- 当所有者离开作用域时,这个值将被丢弃
变量作用域
从声明的那一刻开始直到当前作用域结束时都是有效的。
1 | { // s在这里无效,尚未声明 |
String类型
1 | { |
当s
离开作用域的时候,Rust为我们调用一个特殊的函数,这个函数叫做drop
。
变量与数据交互的方式一:移动
1 | fn main() { |
结果是两个变量都绑定到同一块内存数据。且s1不再有效!
变量与数据交互的方式二:克隆
1 | let s1 = String::from("hello"); |
只在栈上的数据:拷贝
任何一组简单标量的组合都可以实现Copy
。如果一个类型实现了Copt
trait,那么一个旧的变量在将其赋值给其他变量后仍然可用。
1 | let x = 5; |
如果已经实现了Drop
trait,就不能再使用Copy
trait。
所有权与函数
将值传递给函数在语义上与给变量赋值类似。
1 | fn main() { |
也可以返回所有权:
1 | fn main() { |
但是这样就很麻烦了。传递一个变量,后续还想继续用,怎么办呢?Rust对此提供了一个功能,叫做引用。
引用和借用
1 | fn main() { |
但是,借用别人的东西,不可以改变!
1 | fn main() { |
如果要改变,要怎么办呢?
可变引用
1 | fn change(some_string: &mut String) { // 答案是使用可变引用 |
不过可变引用有一个很大的限制:同一时间,只能由一个对某一个特定数据的可变引用。
1 | let mut s = String:from("hello"); |
这样会导致出现数据竞争。
1 | let mut s = String:from("hello"); |
也不能同时有可变与不可变引用:
1 | fn main() { |
但是如果确定不可变引用不再使用,那也是没有问题的。
1 | fn main() { |
悬垂引用
1 | fn dangle() -> &String { |
为什么这里是危险的?因为引用是不会把所有权移出去的,所以s会在函数返回时被丢弃。
1 | fn main() { |
这样确是可以的。因为s的所有权被移交出去了。
总结
- 在任意给定的时间,要么只能有一个可变引用,要么只能由多个不可变引用
- 引用必须总是有效的
- 引用不会传递所有权
切片
slice允许引用集合中一段连续的元素序列。slice 也没有所有权。
字符串slice
1 | let s = String::from("hello world"); |
返回一个slice:
1 | fn first_world(s: &String) -> &str { |
字符串字面量就是slice
1 | let s = "Hello World!"; |
这里的s
的类型就是&str
:指向二进制程序特定位置的slice。
其他类型的slice
1 | let a = [1, 2, 3, 4, 5]; |
枚举与模式匹配
通用概念
模式匹配(Pattern Match)是指判断一个给定的标记序列(Token Sequence)是否存在某种模式(Pattern)的结构的匹配(Match)行为。
- Token Sequence:对应Rust中的表达式:在rust中几乎一切都可以视为表达式
- Pattern:用来匹配表达式的模式语法,用于匹配和绑定变量
- Match:用来触发模式匹配的语法结构
最终效果可以是:1. 变量绑定,2. 条件选择,3. 结构解构
枚举类型
最简单的枚举定义:
1 | enum IpAddr { |
枚举也可以关联数据:
1 | enum IpAddr { |
还可以关联不同的类型:
1 | enum IpAddr { |
枚举也可以定义方法:
1 | impl Message { |
Option枚举
Option是标准库定义的另一个枚举。
Rust中没有空值功能,但是它有一个可以编码存在或不存在概念的枚举,就是Option<T>
。
1 | enum Option<T> { |
例如
1 | let some_number = Some(5); |
为什么 absent_num 不定义为 i32 类型? 这表示它可以为 None!这种i情况下,我们需要处理为 None 的情况。
换个角度:如果一个变量可能为空,那么我们需要把它装在Option
模式匹配
match表达式
1 | enum Coin { |
也可以绑定值,在匹配时我们可以提取值出来:
1 |
|
可以看到,在上面的模式匹配中,除了条件判断外,还实现了变量的绑定
匹配是穷尽的
这里有一个重要的概念,在Rust中,匹配的时候,我们不能只匹配部分。
1 | fn plus_one(x: Option<i32>) -> Option<i32> { |
通配模式和占位模式
1 | let dice_roll = 9; |
1 | let dice_roll = 9; |
if let
为什么要引入if let
?回看match,所有分支都要处理,是不是很麻烦?如果只关心特定分支,怎么办?
match 表达式有时候就可以用 if let 来简化,看例子:
1 | let some_u8_value = Some(0u8); |
与match不同,if let 后面跟的是pattern,语法:if let <pattern> = <Token Sequenc>
。