Rust 入门小记

13 minute read

待更新…

所有权系统

关于所有权系统的三个基本规则

  • 每个值都关联了一个变量,即为其所有者。
  • 每个值只能有一个所有者。
  • 所有者超出范围时,与其关联的值将被删除。

变量范围

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 标量值)
  • 复合型: () (元组) \ [] (数组)

注意事项: iszieusize 两种整数类型类似于 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}