本文属于我的 Rust 学习笔记 系列。
Rust 入门学习笔记以实际例子为主,讲解部分不是从零开始的,所以不建议纯萌新观看,读者最好拥有任意一种面向对象语言的基础,然后自己多多少少看过 Rust 的基本语法,刷过一点 rustlings。
来源:原子之音。当然也包含个人的一些补充。
视频
代码
Rust 进阶学习笔记以及实战的来源则五花八门,将会标注在下一行⬇️。
流程控制与模式匹配
流程
正常情况下,代码是从上到下一行一行执行的,但执行一些操作会导致流程控制改变。
主流流程控制结构有:
- 顺序结构:程序按代码顺序一步一步执行
- 选择结构:根据条件选择不同的路径执行
- if 语句:根据条件执行不同代码块
- switch 语句:根据不同条件执行不同代码块
- 循环结构:重复执行一段代码直到满足某个条件为止
- 跳转结构:跳转到指定位置
if
代码逐行执行,执行流程可被if
改变。
应尽量避免过多嵌套使用,会导致可读性问题。
基本语法:
if condition {
} else {
}
match
Rust 中,match
用于模式匹配,允许更复杂的条件和分支,可处理多个模式,且可以返回值。
基本语法:
match value {
pattern1 => pattern2 if condition => _ => }
相比之下 match 更灵活和清晰,可用于更复杂的场景。
例子
fn main() {
let age = 50;
if age < 50 {
println!("You are young");
} else {
println!("You are old");
}
let scores = 70;
if scores > 90 {
println!("Good!!!");
} else if scores > 60 {
println!("You are OK!");
} else {
println!("Bad!!!");
}
let msg = if age > 50 { "old" } else { "young" };
println!("You are {msg}");
let num = 90;
match num {
80 => println!("80"),
90 => println!("90"),
_ => println!("Some else"),
}
match num {
25..=50 => println!("25 ... 50"),
51..=100 => println!("51 ... 100"),
_ => println!("Some else"),
}
match num {
25 | 50 | 75 => print!("25 or 50 or 75"),
100 | 200 => println!("100 or 200"),
_ => println!("Some else"),
}
match num {
x if x < 60 => println!("bad"),
x if x == 60 => println!("luck"),
_ => println!("Some else"),
}
let num = 60;
let res = match num {
x if x < 60 => "bad".to_owned(),
x if x == 60 => "luck".to_owned(),
_ => "Some else".to_owned(),
};
println!("res value : {res}");
}
循环
循环结构
Rust 提供了如下几种循环结构:
- loop:一个无限循环,通过
break
中断
- while:每次循环检查条件,条件为真时执行循环体
- for:迭代集合或范围,执行代码处理每个元素
for item in iterable {
}
跳出关键字
- break:立即终止循环并跳出循环体,可以通过标签的方式在内层循环体中跳出外层循环
- continue:立即跳过当前循环并开始执行下一次循环
迭代
Rust 的迭代器是一个抽象,通过实现一个 Iterator 特质,提供统一的访问集合元素的方式。
pub trait Iterator {
type Item;
fn next (&mut self) -> Option<Self::Item>;
}
迭代器提供了一系列用于遍历集合元素的方法,如next()
、map()
、filter()
。for
循环就依赖了迭代器的next()
。
循环更适合明确控制循环流程的情况,而迭代器则提供了一种抽象的方式来处理集合元素。
例子
fn main() {
let mut i = 0;
while i < 10 {
println!("{}", i);
i += 1;
}
println!("for");
let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8];
for element in arr {
println!("{}", element);
}
for i in 0..10 {
println!("{}", i);
}
for i in 0..=10 {
println!("{}", i);
}
let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
for element in arr {
if element == 10 {
break;
}
println!("{element}");
}
let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
for element in arr {
if element == 10 {
continue;
}
println!("{element}");
}
'outer: loop {
println!("outer");
loop {
println!("inner");
break 'outer;
}
}
let numbers = [1, 2, 3, 4, 5];
let mut for_numbers = Vec::new();
for &number in numbers.iter() {
let item = number * number;
for_numbers.push(item);
}
println!("for : {:?}", for_numbers);
let numbers = [1, 2, 3, 4, 5].to_vec();
let iter_number: Vec<_> = numbers.iter().map(|&x| x * x).collect();
println!("iter : {:?}", iter_number);
}
函数
函数由fn
关键字声明和定义。函数可以接受 0 或多个参数,每个参数都要指定类型。
函数可以有或没有返回值,有返回值时,通过->
指定返回类型,否则省略或指定空(-> ()
)。函数的最后一行如果不写分号,其结果就会被当作返回值,否则需要用return
。
调用函数需要使用函数名,并传递给函数具体的参数。
main
函数是一个特殊函数,是程序的入口。
copy 特质的函数
如果数据类型实现了copy
特质,则在函数传参时会实现 copy by value 操作。此时会将实参拷贝为形参,形参改变(需要加mut
才能改变)不会影响实参。
例子
fn add(x: i32, y: i32) -> i32 {
x + y
}
fn change_i32(mut x: i32) {
x = x + 4;
println!("fn {x}"); }
fn modify_i32(x: &mut i32) {
*x += 4;
}
#[derive(Copy, Clone)]
struct Point {
x: i32,
y: i32,
}
fn print_point(point: Point) {
println!("point x {}", point.x);
}
fn main() {
let a = 1;
let b = 2;
let c = add(a, b);
println!("c: {c}"); let mut x = 1;
change_i32(x); println!("x {x}"); modify_i32(&mut x); println!("x {x}"); let s = Point { x: 1, y: 2 };
print_point(s); println!("{}", s.x);
}
参数传递
函数值参数传递 move
函数调用时会在栈上开辟一个新的栈帧,用于存储函数的局部变量、参数和返回地址等信息,函数结束后会释放该空间。
当传入 non-copy value(如 Vec String)时,传入函数的实参值的所有权会转移到形参,函数结束时就会释放。说人话就是,move
只能用一次。
不可变借用
借用其实就是其他语言的引用,不过在 Rust 中获取变量的引用一般叫借用。
如果不想失去值的所有权,又没有修改需求,就可以使用不可变借用。
不可变引用可以作为函数的参数,从而在函数内部访问参数值,同时不能修改。这这有助于确保数据的安全性,防止在多处同时对数据进行写操作,从而避免数据竞争。
使用不可变借用需要使用*
解引用(deference),来获取值。
可变借用
可变借用允许在函数内部修改参数的值,执行写操作。同一时间只能有一个可变借用。
需要手动在形参前加&mut
,同样需要使用*
解引用。
例子
fn move_func(p1: i32, p2: String) {
println!("p1 is {}", p1);
println!("p2 is {}", p2);
}
fn print_value(value: &i32) {
println!("{}", value);
}
fn string_func_borrow(s: &String) {
println!("{}", (*s).to_uppercase());
}
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn modify_point(point: &mut Point) {
(*point).x += 2;
point.y += 2;
}
fn main() {
let n = 12;
let s = String::from("oo");
move_func(n, s);
println!("n is {}", n);
let s = String::from("oo");
print_value(&n);
print_value(&n);
string_func_borrow(&s);
println!("n is {} s is {}", n, s);
let mut p = Point { x: 0, y: 0 };
println!("{:?}", p);
modify_point(&mut p);
println!("{:?}", p);
}
函数返回值
函数返回值可以返回值类型,也可以返回引用类型。
值类型返回值
值类型返回值根据是否实现了 copy 分为两种。copy 类型的值通过复制进行返回,通常在栈上进行,不会涉及堆上内存的分配和释放,因此更为高效。
返回引用
返回引用往往需要声明生命周期,除非只有一个传入引用参数/一个返回引用。
慎用静态生命周期。
例子
fn func_copy_back() -> i32 {
let n = 42;
n
}
fn func_non_copy_back() -> String {
let s = String::from("hello");
s
}
fn get_mess(mark: i32) -> &'static str {
if mark == 0 {
"😊😀"
} else {
"≧ ﹏ ≦😫"
}
}
fn main() {
let i = func_copy_back();
println!("{}", i);
let s = func_non_copy_back();
println!("{}", s);
println!("{}", get_mess(i));
}
高阶函数
高阶函数(Higher-Order Functions)就是函数作为函数的参数或返回值。这是函数式编程的重要特性。
自定义高阶函数
函数式特性主要有闭包、迭代器、模式匹配、枚举。其中有些已经介绍过,另外的后续章节会讲到。
集合的高阶函数
- map:对集合中的每个元素应用一个函数,并返回包含结果的新集合。
- filter:根据谓词的结果是否为真过滤集合中的元素
- fold/reduce:迭代集合中的每个元素,并将其积累到一个单一结果中
例子
fn func_twice(f: fn(i32) -> i32, x: i32) -> i32 {
f(f(x))
}
fn mul(x: i32) -> i32 {
x * x
}
fn add(x: i32) -> i32 {
x + 10
}
fn main() {
let result = func_twice(mul, 4);
println!("{result}"); let res = func_twice(add, 10);
println!("{res}"); let numbers = vec![1, 2, 3, 4, 5, 6, 7]; let res: Vec<_> = numbers.iter().map(|&x| x + x).collect();
println!("{:?}", res);
let numbers = vec![1, 2, 3, 4, 5, 6, 7];
let evens = numbers
.into_iter()
.filter(|&x| x % 2 == 0)
.collect::<Vec<_>>();
println!("{:?}", evens);
let numbers = vec![1, 2, 3, 4, 5, 6, 7]; let sum = numbers.iter().fold(0, |acc, &x| acc + x);
println!("Sum: {}", sum);
}