Rust 入门小记
待更新…
所有权系统
关于所有权系统的三个基本规则
- 每个值都关联了一个变量,即为其所有者。
- 每个值只能有一个所有者。
- 所有者超出范围时,与其关联的值将被删除。
变量范围
1{
2 let s = "hello rust!";
3 // s 生效
4}
5// s 失效
变量与数据的交互方式
主要有两种交互方式,分别为移动 (Move)
与克隆 (Clone)
。另外还有一种方式被称之为引用
。
移动
基本数据
1let x = 5; // 将值绑定到 5
2let y = x; // 将 x 的值复制并赋值给 y
上述代码中的5
是基本数据类型的数据,不需要存储到堆中。对于仅在栈中的数据,其移动方式为直接复制,这不会花费更长的时间或更多的存储空间。
非基本数据
关于非基本数据类型的数据,移动方式见下图:
相应代码为:
1let s1 = String::from("hello");
2let s2 = s1;
上述代码中,首行可以认为是类似于长度不确定的数据,因此需要存储在堆中。对于存储在堆中的数据,其移动
方式为移交所有权。
克隆
为降低程序运行成本,每日情况下长度较大的数据都存放在堆中,且采用移动 (移交所有权) 的方式进行数据交互。
如果某些时候只是想将数据复制一份供其他地方使用,则可以使用数据的第二种交互方式 - 克隆。
1fn main() {
2 let s1 = String::from("hello");
3 let s2 = s1.clone();
4}
字面意思,此时的 hello
已经被复制了一份,也就是说 s1 和 s2 分别绑定了一个存储在堆中的值。
引用
引用是变量的间接访问方式,一个变量的值被引用时,其本生依然有效,因为引用并没有在栈中复制变量的值。
引用只能租借值的所有权,而不能获得值的所有权。引用本省也是一个类型并具有一个值,这个值记录的是别的值所在的位置,但引用不具有所指值的所有权。
可变引用与不可变引用
引用分为可变引用
和不可变引用
两种,&
用于取不可变引用。其中不可变引用的表现与其他语言中的指针是完全相反的,其虽然引用租借了值的所有权,但实际只有使用权,无权对数据进行修改。
可变引用使用&mut
进行修饰,两种引用方式除权限不同外,可变引用不允许多重引用,而不可变变量则被允许可以多重引用。
悬垂引用 (Dangling References)
类似与空指针,亦或是野指针。悬垂引用是一种错误,在 Rust 中是不被允许的存在。
1fn main() {
2 let reference_to_nothing = dangle();
3}
4
5fn dangle() -> &String {
6 let s = String::from("hello");
7
8 &s
9}
基本数据类型
- 整数型 (Integer):
i8
\u8
\i16
\u16
\i32
\u32
\i64
\u64
\i128
\u128
\isize
\usize
- 浮点型 (Float):
f32
\f64
- 布尔型:
bool
- 字符型:
char
(4 字节,代表 Unicode 标量值) - 复合型:
()
(元组) \[]
(数组)
注意事项: iszie
与 usize
两种整数类型类似于 golang 中的 int 类型,它们的长度取决于所运行的目标平台。
表示方法
1fn main() {
2 let x = 98_222; // 十进制
3 let x = 0xff; // 十六进制
4 let x = 0o77; // 八进制
5 let x =0b1111_0000; // 二进制
6 let x = b'A' // 字节 (只能表示 u8 型)
7
8 let x = 2.0; // f64
9 let y: f32 = 3.0; // f32
10
11 let tup: (i32, f64, u8) = (500, 6.4, 1); // 元组
12 let (x, y, z) = tup;
13
14 let a = [1, 2, 3]; // 数组
15 let b = ["hello", "rust"];
16 let c: [i32; 5] = [1, 2, 3, 4, 5]; // 长度为 5 的 i32 数组
17 let d = [3; 5]; // [3, 3, 3, 3, 3]
18
19 let mut a = [1, 2, 3]; // 可变数组
20 a[0] = 4;
21}
术语
重影 (Shadowing)
可以理解为重新绑定:
1// result: x = 12
2fn main() {
3 let x = 5;
4 let x = x + 1;
5 let x = x * 2;
6}
可变变量
1// result: x = 10
2fn main() {
3 let mut x = 5;
4 x = x * 2;
5}
不可变变量
1// result: x = 5
2fn main() {
3 let x = 5;
4 x = 10; // 这里将会引起编译器吐槽
5}
自动类型推断
字面意思,即编译器可通过上下文推断未注明类型变量的实际类型。
语句
语句是执行某些操作且没有返回值的步骤。例如:
1let a = 6; // 这是正确的
2let a = (let a = 7); // 这是错误的,因为这个步骤没有返回值
表达式
表达式是有计算且有返回值的步骤。例如:
1a = 7
2b + 2
3c * (c + b)
表达式块 (函数体表达式)
Rust 中可以在一个用 {}
包裹的块中编写一个较为复杂的表达式。对于表达式块,其最后一个步骤是表达式,该表达式的结果值是整个表达式块所代表的值。
1let x = 1;
2let y = {
3 let x = 3;
4 x + y // 注意此行没有分号,否则它将变成一条语句
5}
注意事项: 函数体表达式不能等同于函数体,因为其不能使用 return
关键字。
迭代器 (Iterator)
暂无介绍。
区间
使用..
表示区间,即表示范围的语法。
1..y // 等价于 0..y
2x.. // 等价于 x 位置到数据结束位置
3.. // 等价于从位置 0 到结束位置
流程控制
if-else
语法: if <condition> {block} else {block}
1// result: number = 1
2fn main() {
3 let a = 3;
4 let number = if a > 0 {1} else {-1}; // 结合函数体表达式后,可实现类似三元运算符的效果
5}
while
1while number != 4 {
2 number += 1;
3}
for
1let a = [10,15,20];
2for i in a.iter() { // iter(): 返回 a 的迭代器 (Iterator)
3 println!("{}", i);
4}
1for i in 0..5 {
2 println!("{}", i);
3}
loop
1loop {
2 // ...
3 break;
4}
1let x = loop {
2 break 1;
3}
其他类型 (非基本类型)
切片 (Slice)
1fn main() {
2 let s = String::from("broadcast");
3
4 let part1 = &s[0..5];
5 let part2 = &s[5..9];
6
7 println!("{} = {} + {}", s, part1, part2);
8}
需要注意,被切片引用后字符串被禁止进行值更改。原因在于更改字符串可能会改变其长度,极有可能造成运行时错误。
str 与 String
str
str 是 Rust 核心语言类型,常以引用的形式出现 (&str)。
凡是双引号包裹的字符串常量整体类型性质都是 &str,例如let s = "hello"
。
String
String 类型是 Rust 标准库提供的一种数据类型,其功能相较 str 而言更加完善,支持字符串的追加。
Struct
1struct Site {
2 domain: String,
3 name: String,
4 nation: String,
5 found: u32
6}
1let runoob = Site {
2 domain: String::from("www.runoob.com"),
3 name: String::from("RUNOOB"),
4 nation: String::from("China"),
5 found: 2013
6}
7
8// 简化语法
9let domain = String::from("www.runoob.com");
10let name = String::from("RUNOOB");
11let runoob = Site {
12 domain, // 等同于 domain: ...
13 name, // 等同于 name: ...
14 nation: String::from("China"),
15 found: 2013
16}
17
18// 结果体更新语法
19let runoob = Site {
20 ..runobb // 注意其必须放在最后
21}
元组结构体 (Tuples Struct)
相对于结构体,元组结构体的定义和使用稍显简单。其与元组的区别是它有名字和固定的类型格式。
它存在的意义是为了处理那些需要定义类型但有很简单的数据。
1struct Color(u8, u8, u8);
2struct Point(f64, f64);
3
4let black = Color(0, 0, 0);
5let origin = Point(0.0, 0.0)
Struct Method
1struct Rectangle {
2 width: u32,
3 height: u32
4}
5
6impl Rectangle {
7 fn area(&self) -> u32 {
8 self.width * self.height
9 }
10
11 fn wider(&self, rect: &Rectangle) -> bool {
12 self.width > rect.width
13 }
14}
15
16fn main() {
17 let rect = Rectangle { width: 30, height: 50 };
18 println!("{}", rect.area());
19
20 let rectTwo = Rectangle { width: 40, height: 20 };
21 println!("{}", rect.wider());
22}
结构体关联函数
关联函数和结果体方法基本一致,只是没有 self 参数,非要起一个单独的名字实在令人咋舌。
1#[derive(Debug)]
2
3struct Rectangle {
4 width: u32,
5 height: u32
6}
7
8impl Rectangle {
9 fn create(width: u32, height: u32) -> Rectangle {
10 Rectangle { width, height }
11 }
12}
13
14fn main() {
15 let rect = Rectangle { width: 30, height: 50 };
16 println!("{:#?}", rect.create());
17}
单元结构体 (Unit Struct)
结构体可以只作为一种象征而无需任何成员: struct UnitStruct;
,这种没有身体(成员)的结果体被称为单元结构体。
枚举类
Rust 中的枚举类相对与其他语言中的枚举类要相对强大(复杂)不少,在 Rust 中起着举足轻重的作用。
1enum Book { Papery(u32), Electronic(url: String) }
2
3fn main() {
4 let bookOne = Book::Papery(1001);
5 let bookTwo = Book::Electronic(url: String::from("url://..."));
6}
注意事项: 虽然可以命名,但并不能像结构体字段那样进行访问。
关于所有权的补充
函数参数的所有权机制
1fn main() {
2 let s = String::from("hello");
3
4 takes_ownership(s);
5
6 let x = 5;
7
8 makes_copy(x);
9}
10
11/// 移交所有权
12fn takes_ownership(some_string: String) {
13 println!("{}", some_string);
14}
15
16/// 复制值
17fn makes_copy(some_interger: i32) {
18 println!("{}", some_interger);
19}
函数返回值的所有权机制
1fn main() {
2 let s1 = gives_ownership(); // gibes_ownership 返回值的所有权移交给 s1
3
4 let s2 = String::from("hello"); // s2 被声明有效
5
6 let s3 = takes_and_gives_back(s2); // s2 所有权移交至 takes_and_gives_back,takes_and_gives_back 返回值所有权移交至 s3
7}
8
9fn gives_ownership() -> String {
10 let some_string = String::from("hello");
11 // some_string 被声明有效
12 return some_string;
13 // some_string 被当作返回值返回,此时其所有权一将移交给接收者
14}
15
16fn takes_and_gives_back(a_string: String) -> String {
17 // a_string 被声明有效
18 a_string
19} // 表达式块 (函数体表达式)
结构体所有权
结果体必须掌握字段值的所有权,因为结构体失效时会释放所有字段。如果想要在结构体中使用引用型字段,需要通过“生命周期”机制实现。
一些杂乱的知识
调试库
1#[derive(Debug)] // 导入调试库
2
3struct Rectangle {
4 width: u32,
5 height: u32
6}
7
8fn main() {
9 let rect = Rectangle {
10 width: 30,
11 height: 50
12 };
13 println!("{:?}", rect); // Reacangle { width: 30, height: 50 }
14 println!("{:#?}", react);
15}