rust

rust

参考文献

第一章-rust环境

安装rust

windows

在官网 https://www.rust-lang.org/ 下载,即可自动安装

linux/Mac

1
curl https://sh.rustup.rs -sSf | sh

更新rust

1
rustup update

卸载rust

1
rustup self uninstall

查看rust版本

1
rustc --version

使用本地浏览器离线查看rust文档

1
rustup doc

安装和切换特定版本的rust

例如安装2024-01-31nightly版本的rustc

1
rustup install nightly-2024-01-31

查看安装好的版本列表

1
rustup toolchain list

切换默认rust版本

1
rustup default 版本全名

删除指定rust版本

1
rustup toolchain uninstall 版本全名

查看当前rustc版本

1
rustc --version

hello_world

文件命名规范:小写字母,下划线作空格,后缀名.rs

1
2
3
fn main(){
println!("Hello world!");
}

编译

1
rustc hello_world.rs

运行(windows)

1
.\hello_world.exe

Cargo

Cargo 是 Rust的构建系统和包管理工具

构建代码、下载依赖的库、构建这些库…

安装 Rust 的时候会安装 Cargo,检查cargo的版本

1
cargo --version

使用cargo创建项目

1
cargo new hello_cargo
  • 项目名称是 hello_cargo
  • 会创建一个新的目录 hello_cargo
    • Cargo.toml
    • src 目录
    • 初始化了一个新的Git仓库, .gitignore

Cargo.toml

  • TOML (Tom’s Obvious, Minimal Language)格式,是Cargo的配置格式

  • [pacakge],是一个区域标题,表示下方内容是用来配置包(package)的

1
2
3
4
5
6
7
8
9
10
[package]
name = "hello_cargo" # 项目名
version = "0.1.0" # 项目版本
edition = "2021" # 使用的rust版本
authors = ["cjl <alittletest01@outlook.com>"] # 项目作者

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

[dependencies],另一个区域的开始

它会列出项目的依赖项。在 Rust 里面,代码的包称作 crate。

  • cargo 生成的 main.rs 在 src 目录下
  • 而Cargo.toml在项目顶层下
  • 源代码都应该在 src 目录下
  • 顶层目录可以放置: README、许可信息、配置文件和其它与程序源码无关的文件
  • 如果创建项目时没有使用cargo,也可以把项目转化为使用cargo:
    • 把源代码文件移动到 src 下
    • 创建 Cargo.toml 并填写相应的配置

构建Cargo项目

1
cargo build

会在target/debug/ 生成可执行文件

第一次运行 cargo build 会在顶层目录生成 cargo.lock 文件

  • 该文件负责追踪项目依赖的精确版本
  • 不需要手动修改该文件

构建并运行cargo项目

1
cargo run

检查代码,确保能通过编译,但是不产生任何可执行文件

1
cargo check

cargo check 要比 cargo build 快得多

  • 编写代码的时候可以连续反复的使用cargo check检查代码,提高效率

为发布构建

1
cargo build --release
  • 编译时会进行优化代码
    • 会运行的更快,但是编译时间更长
    • 会在 target/release 而不是 target/debug 生成可执行文件

第二章-初识rust

你会在本章看到一个猜数程序的例子,感受rust语言的特点

其中具体的语法会在第三章开始逐一详细解释

一次猜测

1
2
3
4
5
6
7
8
9
10
11
12
use std::io;
fn main() {
println!("猜数!");
println!("猜测一个数");
let mut foo=1;
foo=2;
let bar=foo;
//bar=1;//不能修改不可变的量
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("无法读取行");
println!("你猜测的数是:{}",guess);
}

可以在https://crates.io/crates 查看rust的库

添加rand库

在Cargo.toml修改以下内容

1
2
[dependencies]
rand = "0.3.14"

生成神秘数字并猜测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use std::{cmp::Ordering, io};

use rand::Rng;
fn main() {
println!("猜数!");
//这里secret_number的类型本是i32,根据下面16行guess.cmp推断类型为u32
let secret_number = rand::thread_rng().gen_range(1, 101);
println!("神秘数字是:{}",secret_number);
println!("猜测一个数");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("无法读取行");
//shadow
let guess: u32 = guess.trim().parse().expect("Please type a number");
//trim可以去除空格,\t和\n
println!("您猜测的数是:{}",guess);
match guess.cmp(&secret_number){
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big"),
Ordering::Equal => println!("You win"),
}

}

允许多次猜测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use std::{cmp::Ordering, io};

use rand::Rng;
fn main() {
println!("猜数!");
//这里secret_number的类型本是i32,根据下面16行guess.cmp推断类型为u32
let secret_number = rand::thread_rng().gen_range(1, 101);
//println!("神秘数字是:{}",secret_number);
loop
{
println!("猜测一个数");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("无法读取行");
//shadow
let guess: u32 = match guess.trim().parse(){
Ok(num)=>num,
Err(_)=>continue,
};
//trim可以去除空格,\t和\n
println!("您猜测的数是:{}",guess);
match guess.cmp(&secret_number){
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big"),
Ordering::Equal => {
println!("You win!");
break;
},
}
}

}

第三章-通用的编程概念

变量与可变性

变量

  • 声明变量使用 let 关键字
  • 默认情况下,变量是不可变的(Immutable)
  • 声明变量时,在变量前面加上 mut,就可以使变量可变。

常量

常量(constant) ,常量在绑定值以后也是不可变的,但是它与不可变的变量有很多区别:

  • 不可以使用 mut,常量永远都是不可变的
  • 声明常量使用 const关键字,它的类型必须被标注
  • 常量可以在任何作用域内进行声明,包括全局作用域
  • 常量只可以绑定到常量表达式,无法绑定到函数的调用结果或只能在运行时才能计算出的值

在程序运行期间,常量在其声明的作用域内一直有效

命名规范:Rust 里常量使用全大写字母,每个单词之间用下划线分开,例如:

MAX_POINTS

例子:

1
const MAX_POINTS : u32 = 100_000

隐藏(shadowing)

  • 可以使用相同的名字声明新的变量,新的变量就会shadow (隐藏)之前声明的同名变量
    • 在后续的代码中这个变量名代表的就是新的变量
  • shadow 和把变量标记为 mut 是不一样的:
    • 如果不使用let关键字,那么重新给非mut的变量赋值会导致编译时错误
    • 而使用let声明的同名新变量,也是不可变的
    • 使用 let 声明的同名新变量,它的类型可以与之前不同

示例代码:

1
2
3
4
5
6
fn main() {
let x = 5;
let x = x + 1;
let x = x * 2;
println!("The value of x is {}", x);
}

小提示: 使用shift+alt+F可以格式化代码

1
2
3
4
5
fn main() {
let spaces = " ";
let spaces = spaces.len();
println!("{}", spaces);
}

数据类型:标量类型

Rust是静态编译语言,在编译时必须知道所有变量的类型

  • 基于使用的值,编译器通常能够推断出它的具体类型
  • 但如果可能的类型比较多(例如把String转为整数的parse方法) ,就必须添加类型的标注,否则编译会报错

例如:

1
2
3
4
fn main() {
//let guess ="42".parse().expect("not a number");//报错
let guess: i32 = "42".parse().expect("not a number");
}

小提示: 使用shift+alt+↑shift+alt+↓可以复制当前行的代码

一个标量类型代表一个单个的值

Rust 有四个主要的标量类型:

整数类型

整数类型枚举

arch是architecture的缩写,isize和usize的位数由程序所运行的计算机架构决定,使用 isize 或 usize 的主要场景是对某种集合进行索引操作。

整数字面值

整数字面值

除了byte类型外,所有的数值字面值都允许使用类型后缀,例如57u8

如果你不太清楚应该使用那种类型,可以使用Rust相应的默认类型:

整数的默认类型就是 i32: 总体上来说速度很快,即使在64位系统中

整数溢出

例如:U8 的范围是 0-255,如果你把一个 U8 变量的值设为 256,那么:

  • 调试模式下编译:

    Rust会检查整数溢出,如果发生溢出,程序在运行时就会panic

  • 发布模式下(–release)编译:

    Rust不会检查可能导致panic的整数溢出

浮点类型

Rust有两种基础的浮点类型,也就是含有小数部分的类型

  • f32,32位,单精度
  • f64,64位,双精度

Rust的浮点类型使用了IEEE-754标准来表述,f64是默认类型, 因为在现代CPU上f64和f32的速度差不多,而且精度更高。

rust的加减乘除取余操作和其他语言是一样的

布尔类型

  • Rust的布尔类型也有两个值: true和false
  • 一个字节大小
  • 符号是 bool

字符类型

  • Rust 语言中 char 类型被用来描述语言中最基础的单个字符。
  • 字符类型的字面值使用单引号
  • 占用4字节大小
  • 是Unicode标量值,可以表示比ASCIl 多得多的字符内容:拼音、中日韩文、零长度空白字符、emoji 表情等。
    • U+0000 到 U+D7FF
    • U+E000 到 U+10FFFF
  • 但Unicode中并没有“字符”的概念,所以直觉上认为的字符也许与Rust中的概念并不相符
1
2
3
4
5
fn main() {
let x = 'z';
let y = '字';
let z = '🦀';
}

数据类型:复合类型

tuple

用小括号包围, 逗号分隔, 长度固定, 和python的元组相同

1
2
3
4
5
6
7
8
9
fn main() {
//定义tuple
let tup: (i32, f64, u8) = (500, 6.4, 1);
//访问tuple的元素
println!("{},{},{}", tup.0, tup.1, tup.2);
//获取tuple的元素值
let (x, y, z) = tup;
println!("{},{},{}", x, y, z);
}
1
2
500,6.4,1
500,6.4,1

数组

  • 数组也可以将多个值放在一个类型里
  • 数组中每个元素的类型必须相同
  • 数组的长度也是固定的

数组的用处

  • 如果想让你的数据存放在stack (栈)上而不是heap (堆)上,或者想保证有固定数量的元素,这时使用数组更有好处
  • 数组没有Vector灵活(以后再讲)。
    • Vector 和数组类似,它由标准库提供
    • Vector 的长度可以改变
    • 如果你不确定应该用数组还是 Vector,那么估计你应该用 Vector。
1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
let a1 = [1, 2, 3, 4, 5];
let a2 = [1; 5]; //[1,1,1,1,1]
//访问数组元素
println!("{}", a1[1]);
//数组越界问题
//let b=a2[5];//会报错
let b = a2[a1[4]];
println!("{}", b); //不会报错,但是运行时会pannic
let c = [1, 2, 3];
}

可以看出,rust在编译的时候只会对数组越界作简单的检查

函数和注释

1
2
3
4
5
6
7
8
9
10
fn main() {
let a = test01(4);
println!("{}", a);
}
fn test01(x: i32) -> i32 {
//必须指明输入参数类型和返回值类型
println!("this is function");
//return x + 5;
x + 5
}

注释的方法有///* xxx */

更多注释写法

if-else

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
let num = 6;
if num % 4 == 0 {
println!("number is divisible by 4");
} else if num % 3 == 0 {
println!("number is divisible by 3");
} else if num % 2 == 0 {
println!("number is divisible by 2");
} else {
println!("number is not divisible by 4,3,2");
}
}

值得注意的是,if语句只允许接收bool语句作为条件, if 1是不合法的

当if-else较多时,建议使用match重构代码

1
2
3
4
5
6
fn main() {
let condition = true;
let a = if condition { 5 } else { 6 };
//let a=if condition {5} else {"6"};//if 和 else 的返回结果不一致,无法确定a的类型,报错
println!("{}", a);
}

循环

loop

1
2
3
4
5
6
7
8
9
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
}

while

1
2
3
4
5
6
7
8
fn main() {
let mut num=3;
while num !=0{
println!("{}",num);
num-=1;
}
println!("LIFTOFF!");
}
1
2
3
4
3
2
1
LIFTOFF!

for

1
2
3
4
5
6
7
fn main() {
let arr = [1, 2, 4, 8];
for a in arr.iter() {
println!("{}", a);
}
}

1
2
3
4
1
2
4
8
1
2
3
4
5
6
fn main() {
for num in (1..4).rev() {
println!("{}", num);
}
println!("LIFTOFF!");
}
1
2
3
4
3
2
1
LIFTOFF!

第四章-所有权

  • Rust 的核心特性就是所有权
  • 所有程序在运行时都必须管理它们使用计算机内存的方式
    • 有些语言有垃圾收集机制,在程序运行时,它们会不断地寻找不再使用的内存
    • 在其他语言中,程序员必须显式地分配和释放内存
  • Rust 采用了第三种方式:
    • 内存是通过一个所有权系统来管理的,其中包含一组编译器在编译时检查的规则。
    • 当程序运行时,所有权特性不会减慢程序的运行速度。

Stack vs heap

计组的内容,这里略

所有权解决的问题:

  • 跟踪代码的哪些部分正在使用 heap 的哪些数据
    • 最小化heap上的重复数据量
    • 清理 heap上未使用的数据以避免空间不足。
  • 一旦你懂的了所有权,那么就不需要经常去想 stack 或 heap 了。
  • 但是知道管理 heap数据是所有权存在的原因,这有助于解释它为什么会这样工作。

所有权规则,内存与分配

所有权规则

  • 每个值都有一个变量,这个变量是该值的所有者
  • 每个值同时只能有一个所有者
  • 当所有者超出作用域(scope)时,该值将被删除。

内存与分配

1
2
3
4
5
fn main() {
let mut s = String::from("hello");
s.push_str(",world");
println!("{}", s);
}
1
hello,world
  • 字符串字面值,在编译时就知道它的内容了,其文本内容直接被硬编码到最终的可执行文件里
    • 速度快、高效。是因为其不可变性。
  • String类型,为了支持可变性,需要在heap上分配内存来保存编译时未知的文本内容:
    • 操作系统必须在运行时来请求内存,这步通过调用 String::from 来实现
    • 当用完 String 之后,需要使用某种方式将内存返回给操作系统
    • 这步,在拥有 GC的语言中,GC会跟踪并清理不再使用的内存
    • 没有GC,就需要我们去识别内存何时不再使用,并调用代码将它返回。
      • 如果忘了,那就浪费内存。
      • 如果提前做了,变量就会非法
      • 如果做了两次,也是Bug。必须一次分配对应一次释放

Rust采用了不同的方式:对于某个值来说,当拥有它的变量走出作用范围时,内存会立即自动的交还给操作系统。(例子)

string的移动

一个String 由3部分组成:

  • 一个指向存放字符串内容的内存的指针
  • 一个长度
  • 一个容量

上面这些东西放在stack上。存放字符串内容的部分在heap上

长度len,就是存放字符串内容所需的字节数

容量 capacity是指String从操作系统总共申请的内存

当执行下面的代码以移动string

string的复制

为了保证内存安全:

  • Rust没有尝试复制被分配的内存
  • Rust 让 s1 失效。当 s1 离开作用域的时候, Rust 不需要释放任何东西

你也许会将复制指针、长度、容量视为浅拷贝,但由于Rust让s1失效了,所以我们用一个新的术语:移动(Move)

隐含的一个设计原则: Rust不会自动创建数据的深拷贝, 就运行时性能而言,任何自动赋值的操作都是廉价的

string的克隆

1
2
3
4
5
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone();
println!("{},{}", s1, s2);
}

string的克隆

拥有Copy trait的类型

  • 任何简单标量的组合类型都可以是 Copy的

  • 任何需要分配内存或某种资源的都不是 Copy 的

  • 一些拥有 Copy trait 的类型:

    • 所有的整数类型,例如 u32
    • bool
    • char
    • 所有的浮点类型,例如 f64
    • Tuple (元组) ,如果其所有的字段都是Copy的
      • (i32, String) 不是
      • (i32, i32) 是

所有权和函数

函数在传入值和返回值的过程中都会发生所有权的转移

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
let s1 = gives_ownership();
let s2 = String::from("hello");
let s3 = takes_and_gives_back(s2);
}
fn gives_ownership() -> String {
let some_string = String::from("hello");
some_string
}
fn takes_and_gives_back(a_string: String) -> String {
a_string
}

引用和借用

&符号就表示引用:允许你引用某些值而不取得其所有权

引用默认是不可变的, 可变引用可以修改引用的数据,写作&mut

引用

1
2
3
4
5
6
7
8
9
10
fn main() {
let mut s1 = String::from("hello");
let len = calculate_length(&mut s1);
println!("The length of {} is {}", s1, len);
}
fn calculate_length(s: &mut String) -> usize {
s.push_str(",world!");
s.len()
}

1
The length of hello,world! is 12

可变引用有一个重要的限制:在特定作用域内,对某一块数据,只能有一个可变的引用。这样做的好处是可在编译时防止数据竞争。

在任意给定时间,要么只能有一个可变引用,要么只能有任意数量的不可变引用。

1
2
3
4
5
6
fn main() {
let mut s = String::from("hello");
let r1 = &s; // 不可变引用
let r2 = &mut s; // 可变引用
println!("{}, {}", r1, r2); // 错误:不能同时使用不可变引用和可变引用
}

以下三种行为下会发生数据竞争:

  • 两个或多个指针同时访问同一个数据
  • 至少有一个指针用于写入数据
  • 没有使用任何机制来同步对数据的访问

可以通过创建新的作用域,来允许非同时的创建多个可变引用

1
2
3
4
5
6
7
fn main() {
let mut s = String::from("hello");
{
let s1 = &mut s;
}
let s2 = &mut s;
}

悬空引用: Dangling References

在 Rust 里,编译器可保证引用永远都不是悬空引用:如果你引用了某些数据,编译器将保证在引用离开作用域之前数据不会离开作用域,否则会报错

1
2
3
4
5
6
7
8
fn main() {
let r = dangle();
}
fn dangle() -> &String {
//编译器会报错
let s = String::from("hello");
&s
}

切片

切片(slice),是另外一种不持有所有权的数据类型

字符串切片的使用

1
2
3
4
5
6
7
8
9
10
11
fn main() {
let s = String::from("hello world");
let hello = &s[..5];
let world = &s[6..];
let whole = &s[..];
let llo = &s[2..5];
println!("{}", hello);
println!("{}", world);
println!("{}", whole);
println!("{}", llo);
}

定义一个函数,实现找到字符串的第一个单词

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s);//给word创建了一个不变引用
s.clear();//会报错,因为会创建可变引用,这样就能防止上面提取的第一个单词因为字符串s的修改而过时
println!("{}", word);
}
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[..i];
}
}
&s[..]
}

定义函数时使用字符串切片来代替字符串引用会使我们的API更加通用,且不会损失任何功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fn main() {
let mut my_string = String::from("hello world");
let wordIndex = first_word(&my_string[..]);
let my_string_literal = "hello world";
let wordIndex = first_word(my_string_literal);
}
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[..i];
}
}
&s[..]
}

第五章-struct

定义和实例化struct

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//定义结构体
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
//定义tuple struct
struct Color(i32, i32, i32);
fn main() {
// 实例化struct
let user1 = User {
email: String::from("cjl@algorithmpark.xyz"),
username: String::from("cjl"),
active: true,
sign_in_count: 114514,
};
//取得struct里面的某个值
println!("{}", user1.active);
//struct更新语法:基于某个struct示例来构建一个新实例
let user2 = User {
email: String::from("root@algorithmpark.xyz"),
username: String::from("root"),
..user1
};
//实例化tuple struct
let black = Color(0, 0, 0);
}

如果字段使用了String而不是&str:

  • 该 struct 实例拥有其所有的数据
  • 只要 struct 实例是有效的,那么里面的字段数据也是有效的

struct里也可以存放引用,但这需要使用生命周期(以后讲)。

  • 生命周期保证只要 struct实例是有效的,那么里面的引用也是有效的。
  • 如果 struct 里面存储引用,而不使用生命周期,就会报错(例子)。

结构体案例:长方形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#[derive(Debug)]
struct Rectangle {
width: u32,
length: u32,
}
fn main() {
let rect = Rectangle {
width: 30,
length: 50,
};
println!("{}", area(&rect));
println!("{:#?}", rect);
}
fn area(rect: &Rectangle) -> u32 {
rect.width * rect.length
}
1
2
3
4
5
1500
Rectangle {
width: 30,
length: 50,
}

结构体方法

使用impl块定义方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#[derive(Debug)]
struct Rectangle {
width: u32,
length: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.length * self.width
}
}

fn main() {
let rect = Rectangle {
width: 30,
length: 50,
};
println!("{}", rect.area());
println!("{:#?}", rect);
}
1
2
3
4
5
1500
Rectangle {
width: 30,
length: 50,
}

如果不传入self,可以实现构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#[derive(Debug)]
struct Rectangle {
width: u32,
length: u32,
}
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
length: size,
}
}
}
fn main() {
let rect = Rectangle::square(5);
println!("{:#?}", rect);
}

第六章-枚举类型

定义枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
enum IpAddrKind {
V4,
V6,
}
fn main() {
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
route(four);
route(six);
route(IpAddrKind::V4);
}
fn route(ip_kind: IpAddrKind) {}

将数据附加到枚举的变体中

1
2
3
4
5
6
7
8
enum IpAddrKind{
V4(u8,u8,u8,u8),
V6(String),
}
fn main(){
let home = IpAddrKind::V4(127, 0, 0, 1);
let loopback = IpAddrKind::V6(String::from("::1"));
}

更加复杂的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn call(&self) {}
}

fn main() {
let q = Message::Quit;
let m = Message::Move { x: 12, y: 24 };
let w = Message::Write(String::from("Hello"));
let c = Message::ChangeColor(0, 255, 255);
m.call();
}

option枚举

标准库中的定义

1
2
3
4
enum Option<T>{
Some(T),
None,
}

它包含在Prelude(预导入模块)中,可以直接使用

  • Option<T>
  • Some(T)
  • None
1
2
3
4
5
fn main(){
let x:i8=5;
let y:Option<i8> =Some(5);
let sum=x+y;//会报错
}

Option的作用是作为null,因为它的类型无法和正常的类型计算,它可以避免和null作计算

match

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!("Penny!");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
fn main() {
println!("{}", value_in_cents(Coin::Penny));
}
1
2
Penny!
1

绑定值的模式匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!("Penny!");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
}
}
}
fn main() {
let c = Coin::Quarter(UsState::Alaska);
println!("{}", value_in_cents(c));
}
1
2
State quarter from Alaska!
25

match 要求枚举所有的可能, 如果没有全部枚举, 会报错

可以使用_表示通配剩余的情况

1
2
3
4
5
6
7
8
9
10
fn main() {
let v = 0u8;
match v {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
_ => (),
}
}

if let

if let 可以作为一种特殊的match的简写

1
2
3
4
5
6
7
8
9
10
11
fn main() {
let v = Some(0u8);
//下面两段代码作用相同
match v {
Some(3) => println!("three"),
_ => (),
}
if let Some(3) = v {
println!("three");
}
}

if let可以搭配else

1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
let v = Some(0u8);
//下面两段代码作用相同
match v {
Some(3) => println!("three"),
_ => println!("others"),
}
if let Some(3) = v {
println!("three");
} else {
println!("others");
}
}

第七章-模块系统

package,Crate,Module

代码组织主要包括:

  • 哪些细节可以暴露,哪些细节是私有的
  • 作用域内哪些名称有效

模块系统

  • Package(包):Cargo 的特性,让你构建、测试、共享 crate
  • Crate (单元包):一个模块树,它可产生一个 library 或可执行文件
  • Module (模块)、use:让你控制代码的组织、作用域、私有路径
  • Path (路径) :为struct、function或 module等项命名的方式

Crate 的类型:

  • binary
  • library

Crate Root:

  • 是源代码文件
  • Rust 编译器从这里开始,组成你的 Crate 的根 Module

一个 Package :

  • 包含1个Cargo.toml,它描述了如何构建这些 Crates
  • 只能包含 0-1 个 library crate
  • 可以包含任意数量的 binary crate
  • 但必须至少包含一个 crate (library 或 binary)

Cargo的惯例

  • src/main.rs:
    • binary crate的 crate root
    • crate 名与 package 名相同
  • src/lib.rs:
    • crate 名与 package 名相同
    • library crate 的 crate root
    • package包含一个 library crate

Cargo把 crate root 文件交给 rustc 来构建library或 binary

  • 一个 Package 可以同时包含 src/main.rs 和 src/lib.rs
    • 一个 binary crate,一个library crate
    • 名称与package名相同
  • 一个Package可以有多个binary crate:
    • 文件放在 src/bin
    • 每个文件是单独的 binary crate

Carte将相关功能组合到一个作用域内,便于在项目间进行共享, 防止冲突

定义module来控制作用域和私有性

  • Module:
    • 在一个crate 内,将代码进行分组
    • 增加可读性,易于复用
    • 控制项目(item)的私有性。public、 private
  • 建立 module
    • mod 关键字
    • 可嵌套
    • 可包含其它项(struct、enum、常量、trait、函数等)的定义

在lib.rs创建模块

1
2
3
4
5
6
7
8
9
10
11
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}

crate树形结构

path路径

  • 为了在 Rust 的模块中找到某个条目,需要使用路径。

  • 路径的两种形式:

    • 绝对路径:从 crate root 开始,使用 crate 名 或 字面值 crate
    • 相对路径:从当前模块开始,使用 self, super 或当前模块的标识符
  • 路径至少由一个标识符组成,标识符之间使用:。

私有边界(privacy boundary)

  • 模块不仅可以组织代码,还可以定义私有边界。
  • 如果想把函数或 struct等设为私有,可以将它放到某个模块中。
  • Rust中所有的条目(函数,方法, struct, enum,模块,常量)默认是私有的。
  • 父级模块无法访问子模块中的私有条目
  • 子模块里可以使用所有祖先模块中的条目

使用 pub 关键字来将某些条目标记为公共的

1
2
3
4
5
6
7
8
9
10
11
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
//绝对路径
crate::front_of_house::hosting::add_to_waitlist();
//相对路径
front_of_house::hosting::add_to_waitlist();
}

super

1
2
3
4
5
6
7
8
9
10
11
12
fn serve_order() {}

mod back_of_house {
fn fix_incorrect_order() {
cook_order();
//相对路径
super::serve_order();
//绝对路径
crate::serve_order();
}
fn cook_order() {}
}

pub struct

  • pub 放在 struct 前:
    • struct 是公共的
    • struct 的字段默认是私有的
  • struct 的字段需要单独设置 pub 来变成共有。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
mod back_of_house {
pub struct Breakfast {
pub toast: String,
seasonal_fruit: String,
}
impl Breakfast {
pub fn summer(toast: &str) -> Breakfast {
Breakfast {
toast: String::from(toast),
seasonal_fruit: String::from("peaches"),
}
}
}
}
pub fn eat_at_restaurant() {
let mut meal = back_of_house::Breakfast::summer("Rye");
meal.toast = String::from("Wheat");
println!("I'd like {} toast please", meal.toast);
//meal.seasonal_fruit = String::from("blueberries");
}
1
I'd like Wheat toast please

pub enum

pub 放在 enum 前:

  • enum 是公共的
  • enum 的变体也都是公共的

use关键字

可以使用 use关键字将路径导入到作用域内

使用use引入自定义函数

1
2
3
4
5
6
7
8
9
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}

使用use引入标准库的struct

1
2
3
4
5
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}

使用as可以添加本地别名

使用 pub use 重新导出名称

使用use将路径(名称)导入到作用域内后,该名称在此作用域内是私有的。

pub use:重导出

  • 将条目引入作用域
  • 该条目可以被外部代码引入到它们的作用域

使用外部包(package)

  1. Cargo.toml添加依赖的包(package)
  2. use将特定条目引入作用域

嵌套路径以简写use语句

1
2
use std::{cmp::Ordering, io};
use std::io::{self,Write};

通配符*

1
use std::collections::*;

给cargo换源

~\.cargo\打开命令行

1
touch config

添加以下内容

1
2
3
4
5
6
7
8
9
[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"

replace-with = 'tuna'
[source.tuna]
registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git"

[net]
git-fetch-with-cli = true

将模块内容放到其他文件

  • 模块定义时,如果模块名后边是; ,而不是代码块:
    • Rust 会从与模块同名的文件中加载内容
    • 模块树的结构不会变化
  • 随着模块逐渐变大,该技术让你可以把模块的内容移动到其它文件中

例如,在lib.rs中有个模块mod front_of_house

1
2
3
4
5
mod front_of_house{
pub mod hosting{
pub fn add_to_waitlist(){}
}
}

可以把上述的代码变为

1
mod front_of_house;

然后再src目录下新建文件front_of_house.rs, 其中内容为

1
2
3
pub mod hosting{
pub fn add_to_waitlist(){}
}

第八章-常用的集合

vector

构造vector

1
2
3
4
5
6
fn main() {
//创建空vector
let v1: Vec<i32> = Vec::new();
//创建vector并初始化内容
let v2 = vec![1, 2, 3];
}

添加元素

1
2
3
4
5
6
7
8
9
fn main() {
//创建空vector, 这里可以不指明类型为Vec<i32>, 编译器可以根据下文push操作推断
let mut v = Vec::new();

v.push(1);
v.push(2);
v.push(3);
v.push(4);
}

访问元素

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
//创建vector并初始化内容
let v = vec![1, 2, 3, 4, 5];
//下标访问,如果越界,运行时会panic
let third: &i32 = &v[2];
println!("The third element is {}", third);
//get访问,如果越界,运行不会panic
match v.get(2) {
Some(third) => println!("The third element is {}", third),
None => println!("There is no third element"),
}
}
1
2
The third element is 3
The third element is 3

下面的代码是会报错的

1
2
3
4
5
6
7
8
fn main() {
//创建vector并初始化内容
let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0];
v.push(6);
println!("{}", first);
}

会出现报错:cannot borrow v as mutable because it is also borrowed as immutable

first首先借用了不可变的引用,随后push操作借用了可变的引用,这样是不安全的,因为push操作过后v存放的内存地址可能会改变,导致first借用的引用失效

遍历和修改

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
//创建vector并初始化内容
let mut v = vec![1, 2, 3, 4, 5];
//修改元素
let first = &mut v[0];
*first = 114514;
//遍历元素
for i in &v {
println!("{}", i);
}
}

1
2
3
4
5
114514
2
3
4
5
1
2
3
4
5
6
7
fn main() {
let mut v = vec![1, 2, 3, 4, 5];
for i in &mut v {
*i += 50;
println!("{}", *i);
}
}
1
2
3
4
5
51
52
53
54
55

配合enum实现存放多种类型数据

1
2
3
4
5
6
7
8
9
10
11
12
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
fn main() {
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(114.514),
];
}

String

  • 来自 标准库 而不是 核心语言
  • 可增长、可修改、可拥有
  • UTF-8 编码

构造

构造空字符串

1
2
3
fn main(){
let s=String::new();
}

构造字符串并初始化

1
2
3
4
5
fn main() {
let s1 = String::from("abc");
let s2 = "def".to_string();
println!("{},{}", s1, s2);
}
1
abc,def

String支持多语言

1
2
3
4
5
6
7
8
9
10
11
fn main() {
let hello = String::from("Dobrý den");
let hello = String::from("Hello");
let hello = String::from("नमस्ते");
let hello = String::from("こんにちは");
let hello = String::from("안녕하세요");
let hello = String::from("你好");
let hello = String::from("Olá");
let hello = String::from("Здравствуйте");
let hello = String::from("Hola");
}

修改

  • push_str() 方法:把一个字符串切片附加到 String
1
2
3
4
5
fn main() {
let mut s = String::from("foo");
s.push_str("bar");
println!("{}", s);
}
1
foobar
  • push() 方法:把单个字符附加到 String
1
2
3
4
5
fn main() {
let mut s = String::from("foo");
s.push('b');
println!("{}", s);
}
1
foob
  • +:连接字符串(例子)
1
2
3
4
5
6
fn main() {
let s1 = String::from("I hate rust");
let s2 = String::from(", I love python");
let s3 = s1 + &s2;
println!("{},{},{}", s1, s2, s3);
}

打印s1处会报错,说明s1的所有权被赋给了s3

  • format!连接字符串
1
2
3
4
5
6
7
fn main() {
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s4 = format!("{}-{}-{}", s1, s2, s3);
println!("{}", s4);
}
1
tic-tac-toe

长度

1
2
3
4
fn main() {
let len = String::from("Hola").len();
println!("{}", len);
}
1
4
1
2
3
4
fn main() {
let len = String::from("你好").len();
println!("{}", len);
}
1
6

索引

rust不允许对String进行索引

  • 索引操作应消耗一个常量时间(O(1))
  • 而String无法保证:需要遍历所有内容,来确定有多少个合法的字符。

切割字符串也要小心,可以使用 [ ] 和 一个范围 来创建字符串的切片

  • 必须谨慎使用
  • 如果切割时跨越了字符边界,程序就会panic。

遍历String

1
2
3
4
5
6
7
8
9
10
fn main() {
let s = String::from("算法乐园");
for i in s.chars() {
println!("{}", i);
}
println!("--------");
for i in s.bytes() {
println!("{}", i);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17




--------
231
174
151
230
179
149
228
185
144
229
155
173

HashMap

构造

1
2
3
4
5
6
use std::collections::HashMap;
fn main() {
//scores的具体类型编译器会根据下面的代码自动推断
let mut scores = HashMap::new();
scores.insert(String::from("CJL"), 59);
}
  • HashMap用的较少,不在Prelude中
  • 标准库对其支持较少,没有内置的宏来创建 HashMap
  • 数据存储在 heap 上
  • 同构的。一个 HashMap 中:
    • 所有的 K 必须是同一种类型
    • 所有的 V 必须是同一种类型

使用collect方法构造

1
2
3
4
5
6
use std::collections::HashMap;
fn main() {
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();
}

所有权问题

  • 对于实现了Copy trait的类型(例如i32) ,值会被复制到HashMap中
  • 对于拥有所有权的值(例如String) ,值会被移动,所有权会转移给HashMap
  • 如果将值的引用插入到 HashMap,值本身不会移动
    • 在 HashMap 有效的期间,被引用的值必须保持有效

访问其中的值

1
2
3
4
5
6
7
8
9
10
11
12
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
let team_name = String::from("Blue");
let score = scores.get(&team_name);
match score {
Some(s) => println!("{}", s),
None => println!("team not exist"),
};
}
1
10

遍历

1
2
3
4
5
6
7
8
9
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
for (k, v) in &scores {
println!("{}:{}", k, v);
}
}
1
2
Blue:10
Yellow:50

修改

  • HashMap 大小可变

  • 每个K同时只能对应一个V

  • 更新 HashMap 中的数据:

  • K 已经存在,对应一个 V

    • 替换现有的 v
    • 保留现有的 V,忽略新的 V
    • 合并现有的 V 和新的 V
1
2
3
4
5
6
7
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);
println!("{:?}", scores);
}
1
{"Blue": 25}
  • K不存在

    • 添加一对 K,V
  • entry 方法:检查指定的K 是否对应一个V

    • 参数为 K
    • 返回 enum Entry:代表值是否存在
  • entry的 or_insert()方法:

    • 返回:
      • 如果K存在,返回到对应的V的一个可变引用
      • 如果 K 不存在,将方法参数作为 K 的新值插进去,返回到这个值的可变引用
1
2
3
4
5
6
7
8
9
10
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
let e = scores.entry(String::from("Yellow"));
println!("{:?}", e);
e.or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);
println!("{:?}", scores);
}
1
2
Entry(VacantEntry("Yellow"))
{"Blue": 10, "Yellow": 50}
1
2
3
4
5
6
7
8
9
10
use std::collections::HashMap;
fn main() {
let text = "What a beautiful girl she is!";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}
println!("{:#?}", map);
}
1
2
3
4
5
6
7
8
{
"What": 1,
"is!": 1,
"beautiful": 1,
"girl": 1,
"she": 1,
"a": 1,
}

Hash函数

  • 默认情况下, HashMap使用加密功能强大的Hash函数,可以抵抗拒绝服务(DoS)攻击。
    • 不是可用的最快的Hash算法
    • 但具有更好安全性。
  • 可以指定不同的hasher来切换到另一个函数。
    • hasher 是实现 BuildHasher trait 的类型

第九章-错误处理

不可修复的错误与panic

  • Rust 的可靠性:错误处理

    • 大部分情况下:在编译时提示错误,并处理
  • 错误的分类:

    • 可恢复
      • 例如文件未找到,可再次尝试
    • 不可恢复
      • bug,例如访问的索引超出范围
  • Rust 没有类似异常的机制

    • 可恢复错误:Result<T, E>
    • 不可恢复:panic!宏
  • 默认情况下,当panic发生:

    • 程序展开调用栈(工作量大)
      • Rust 沿着调用栈往回走
      • 清理每个遇到的函数中的数据
    • 或立即中止调用栈:
      • 不进行清理,直接停止程序
      • 内存需要 OS 进行清理
  • 想让二进制文件更小,把设置从“展开”改为“中止”:

    • 在Cargo.toml中适当的 profile部分设置:
      • panic = ‘abort’
1
2
3
fn main() {
panic!("crash and burn");
}
1
2
3
4
thread 'main' panicked at src\main.rs:2:5:
crash and burn
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\variables.exe` (exit code: 101)

使用panic!产生的回溯信息

  • panic!可能出现在:
    • 我们写的代码中
    • 我们所依赖的代码中
  • 可通过调用 panic!的函数的回溯信息来定位引起问题的代码
  • 通过设置环境变量set RUST_BACKTRACE=1可得到回溯信息
  • 如果你觉得回溯信息不够丰富,可以执行set RUST_BACKTRACE=full
  • 为了获取带有调试信息的回溯,必须启用调试符号(不带–release)

Result枚举与可恢复的错误

1
2
3
4
enum Result<T,E>{
Ok(T),
Err(E),
}
  • T:操作成功情况下, Ok变体里返回的数据的类型

  • E:操作失败情况下, Err变体里返回的错误的类型

打开文件hello.txt,从而产生错误

1
2
3
4
5
6
7
8
9
10
use std::fs::File;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => {
panic!("Error opening file {:?}", error)
}
};
}
1
2
3
4
thread 'main' panicked at src\main.rs:7:13:
Error opening file Os { code: 2, kind: NotFound, message: "系统找不到指 定的文件。" }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\variables.exe` (exit code: 101)

打开文件hello.txt, 如果hello.txt不存在,则新建文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("Error creating file:{:?}", e),
},
oe => panic!("Error opening the file:{:?}", oe),
},
};
}

expect自定义输出错误信息

1
2
3
4
use std::fs::File;
fn main() {
let f = File::open("hello.txt").expect("无法打开文件");
}

返回错误给函数调用者处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use std::fs::File;
use std::io;
use std::io::Read;
fn main() {}
fn read_username_from_file() -> Result<String, io::Error> {
let f = File::open("hello.txt");
let mut f = match f {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e),
}
}

传播错误的快捷方式

使用问号简化代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use std::fs::File;
use std::io;
use std::io::Read;
fn main() {}
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("hello.txt")?;
// let mut f = match f {
// Ok(file) => file,
// Err(e) => return Err(e),
// };
let mut s = String::new();
// match f.read_to_string(&mut s) {
// Ok(_) => Ok(s),
// Err(e) => Err(e),
// }
f.read_to_string(&mut s)?;
Ok(s)
}

?与from函数

  • Trait std::convert::From上的from函数:
    • 用于错误之间的转换
  • ?所应用的错误,会隐式的被from函数处理
  • ?调用 from 函数时:
    • 它所接收的错误类型会被转化为当前函数返回类型所定义的错误类型
  • 用于: 针对不同错误原因,返回同一种错误类型
    • 只要每个错误类型实现了转换为所返回的错误类型的from函数
  • ?运算符只能用于返回Result的函数

上面的代码可以进一步简化

1
2
3
4
5
6
7
8
9
use std::fs::File;
use std::io;
use std::io::Read;
fn main() {}
fn read_username_from_file() -> Result<String, io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}

在main函数中使用?运算符

  • main 函数返回类型是:()
  • main 函数的返回类型也可以是:Result<T, E>
  • Box<dyn Error> 是 trait 对象:
    • 简单理解: “任何可能的错误类型”
1
2
3
4
5
use std::{error::Error, fs::File};
fn main() -> Result<(), Box<dyn Error>> {
let f = File::open("hello.txt")?;
Ok(())
}

何时使用panic!

  • 在定义一个可能失败的函数时,优先考虑返回 Result
  • 否则就 panic!

第十章-泛型-trait-生命周期

泛型

函数

1
2
3
4
5
6
7
8
fn lenth<T>(v: &[T]) -> i32 {
v.len() as i32
}
fn main() {
let nums: Vec<i32> = vec![1, 2, 3, 4, 5];
let n = lenth(&nums);
println!("{}", n);
}
1
5

struct

1
2
3
4
5
6
7
struct Point<T>{
x:T,
y:T,
}
fn main(){
let integer= Point{x:5,y:1};
}

enum

1
2
3
4
5
6
7
enum Option<T> {
Some(T),
None,
}
fn main() {
let op = Option::Some(4);
}

trait

  • Trait 告诉 Rust编译器:

    • 某种类型具有哪些并且可以与其它类型共享的功能
  • Trait:抽象的定义共享行为

  • Trait bounds (约束) :泛型类型参数指定为实现了特定行为的类型

  • Trait与其它语言的接口(interface)类似,但有些区别。

  • Trait的定义:把方法签名放在一起,来定义实现某种目的所必需的一组行为。

    • 关键字:trait
    • 只有方法签名,没有具体实现
    • trait可以有多个方法:每个方法签名占一行,以;结尾
    • 实现该trait的类型必须提供具体的方法实现
1
2
3
4
5
pub trait Summary {
fn summarize(&self) -> String;
fn summarize1(&self) -> String;
}
fn main() {}

示例:

lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
pub trait Summary{
fn summarize(&self)->String;
}
pub struct NewsArticle{
pub headline:String,
pub location:String,
pub author:String,
pub content:String,
}
impl Summary for NewsArticle{
fn summarize(&self)->String {
format!("{}, by {} ({})",self.headline,self.author,self.location)
}
}
pub struct Tweet{
pub username:String,
pub content:String,
pub reply:bool,
pub retweet:bool,
}
impl Summary for Tweet{
fn summarize(&self)->String {
format!("{}:{}",self.username,self.content)
}
}

main.rs

1
2
3
4
5
6
7
8
9
10
11
12
//use variables::NewsArticle;
use variables::Tweet;
use variables::Summary;
fn main() {
let tweet = Tweet{
username:String::from("horse_ebooks"),
content:String::from("of course, as you probably already know, people"),
reply:false,
retweet:false,
};
println!("1 new tweet:{}",tweet.summarize());
}

注:这里的variables是本package的name, 在Cargo.toml中定义

可以给trait Summary实现默认的函数summarize

默认实现的方法可以调用trait中其它的方法,即使这些方法没有默认实现。注意:无法从方法的重写实现里面调用默认的实现。

impl trait语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
pub trait Summary{
fn summarize(&self)->String;
}
pub struct NewsArticle{
pub headline:String,
pub location:String,
pub author:String,
pub content:String,
}
impl Summary for NewsArticle{
fn summarize(&self)->String {
format!("{}, by {} ({})",self.headline,self.author,self.location)
}
}
pub struct Tweet{
pub username:String,
pub content:String,
pub reply:bool,
pub retweet:bool,
}
impl Summary for Tweet{
fn summarize(&self)->String {
format!("{}:{}",self.username,self.content)
}
}
//可以传入NewsArticle 或 Tweet
pub fn notify(item:impl Summary){
println!("Breaking news!, {}", item.summarize());
}

trait bound语法

把上面的notify函数改成这样

1
2
3
pub fn notify<T: Summary>(item: T) {
println!("Breaking news!, {}", item.summarize());
}

指定多个trait

1
2
3
4
5
6
7
use std::fmt::Display;
pub fn notify1(item: impl Summary + Display) {
println!("Breaking news!, {}", item.summarize());
}
pub fn notify2<T: Summary + Display>(item: T) {
println!("Breaking news!, {}", item.summarize());
}

为了避免函数的签名过长,可以使用下面notify2的写法

1
2
3
4
5
6
7
8
9
10
11
12
use std::fmt::Debug;
use std::fmt::Display;
pub fn notify1<T: Summary + Display, U: Clone + Debug>(item1: T, item2: U) -> String {
format!("Breaking news!, {}", item1.summarize())
}
pub fn notify2<T, U>(item1: T, item2: U) -> String
where
T: Summary + Display,
U: Clone + Debug,
{
format!("Breaking news!, {}", item1.summarize())
}

生命周期

这部分比较抽象,建议去看视频


rust
https://blog.algorithmpark.xyz/2024/02/08/language/rust/index/
作者
CJL
发布于
2024年2月8日
更新于
2024年2月26日
许可协议