From 097735c734424443483703b2b04f491a22fa1618 Mon Sep 17 00:00:00 2001 From: wenyongda Date: Tue, 25 Mar 2025 16:37:48 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/_posts/Rust.md | 579 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 579 insertions(+) diff --git a/source/_posts/Rust.md b/source/_posts/Rust.md index 874cd78..383d521 100644 --- a/source/_posts/Rust.md +++ b/source/_posts/Rust.md @@ -474,6 +474,10 @@ Hello, another! ## 函数参数 +```rust +fn <函数名> ( <参数> ) <函数体> +``` + Rust 中定义函数如果需要具备参数必须声明参数名称和类型: ```rust @@ -531,6 +535,458 @@ fn main() { } ``` +# Rust 条件语句 + +Rust 中条件语句格式是这样的: + +```rust +fn main() { + let number = 3; + if number < 5 { + println!("条件为 true"); + } else { + println!("条件为 false"); + } +} +``` + +上述程序中,条件表达式 number < 5 不需要用小括号包括(注意,不需要不是不允许);但是 Rust 中的 if 不存在单语句不用加 {} 的规则,不允许使用一个语句代替一个块。尽管如此,Rust还是支持传统 else-if 语法的: + +```rust +fn main() { + let a = 12; + let b; + if a > 0 { + b = 1; + } + else if a < 0 { + b = -1; + } + else { + b = 0; + } + println!("b is {}", b); +} +``` + +运行结果: + +```rust +b is 1 +``` + +Rust 中的条件表达式必须是 bool 类型,例如下面的程序是错误的: + +```rust +fn main() { + let number = 3; + if number { *// 报错,expected `bool`, found integerrustc(E0308)* + println!("Yes"); + } +} +``` + +虽然 C/C++ 语言中的条件表达式用整数表示,非 0 即真,但这个规则在很多注重代码安全性的语言中是被禁止的。 + +结合之前章学习的函数体表达式我们加以联想: + +```rust +if { block 1 } else { block 2 } +``` + +在 Rust 中我们可以使用 if-else 结构实现类似于三元条件运算表达式 **(A ? B : C)** 的效果: + +```rust +fn main() { + let a = 3; + let number = if a > 0 { 1 } else { -1 }; + println!("number 为 {}", number); +} +``` + +运行结果: + +``` +number 为 1 +``` + +if 语句块的返回值也可以给 number 进行赋值 + +用 if 来赋值时,要保证每个分支返回的类型一样(这种说法也不完全准确),此处返回的 `5` 和 `6` 就是同一个类型,如果返回类型不一致就会报错 + +```rust +fn main() { + let condition = true; + let number = if condition { + 5 + } else { + 6 + }; + + println!("The value of number is: {}", number); +} +``` + +# Rust 循环 + +Rust 的循环结果设计也十分成熟。 + +## while 循环 + +while 循环是最典型的条件语句循环: + +```rust +fn main() { + let mut number = 1; + while number != 4 { + println!("{}", number); + number += 1; + } + println!("EXIT"); +} +``` + +运行结果: + +``` +1 +2 +3 +EXIT +``` + +在 C 语言中 for 循环使用三元语句控制循环,但是 Rust 中没有这种用法,需要用 while 循环来代替: + +C 语言 + +```c +int i; +for (i = 0; i < 10; i++) { + *// 循环体* +} +``` + +Rust + +```rust +let mut i = 0; +while i < 10 { + *// 循环体* + i += 1; +} +``` + +## for 循环 + +for 循环是最常用的循环结构,常用来遍历一个线性数据结构(比如数组)。for 循环遍历数组: + +```rust +fn main() { + let a = [10, 20, 30, 40, 50]; + for i in a.iter() { + println!("值为 : {}", i); + } +} +``` + +运行结果: + +``` +值为 : 10 +值为 : 20 +值为 : 30 +值为 : 40 +值为 : 50 +``` + +这个程序中的 for 循环完成了对数组 a 的遍历。a.iter() 代表 a 的迭代器(iterator),在学习有关于对象的章节以前不做赘述。 + +当然,for 循环其实是可以通过下标来访问数组的: + +```rust +fn main() { +let a = [10, 20, 30, 40, 50]; + for i in 0..5 { + println!("a[{}] = {}", i, a[i]); + } +} +``` + +运行结果: + +``` +a[0] = 10 +a[1] = 20 +a[2] = 30 +a[3] = 40 +a[4] = 50 +``` + +## loop 循环 + +某个循环无法在开头和结尾判断是否继续进行循环,必须在循环体中间某处控制循环的进行。如果遇到这种情况,我们经常会在一个 while (true) 循环体里实现中途退出循环的操作。 + +Rust 语言有原生的无限循环结构 —— loop: + +```rust +fn main() { + let s = ['R', 'U', 'N', 'O', 'O', 'B']; + let mut i = 0; + loop { + let ch = s[i]; + if ch == 'O' { + break; + } + println!("\'{}\'", ch); + i += 1; + } +} +``` + +运行结果: + +``` +'R' +'U' +'N' +``` + +loop 循环可以通过 break 关键字类似于 return 一样使整个循环退出并给予外部一个返回值。这是一个十分巧妙的设计,因为 loop 这样的循环常被用来当作查找工具使用,如果找到了某个东西当然要将这个结果交出去: + +```rust +fn main() { + let s = ['R', 'U', 'N', 'O', 'O', 'B']; + let mut i = 0; + let location = loop { + let ch = s[i]; + if ch == 'O' { + break i; + } + i += 1; + }; + println!(" \'O\' 的索引为 {}", location); +} +``` + +运行结果: + +``` + 'O' 的索引为 3 +``` + +# Rust 闭包 + +Rust 中的闭包是一种匿名函数,它们可以捕获并存储其环境中的变量。 + +闭包允许在其定义的作用域之外访问变量,并且可以在需要时将其移动或借用给闭包。 + +闭包在 Rust 中被广泛应用于函数编程、并发编程、和事件驱动编程等领域。 + +闭包在 Rust 中非常有用,因为它们提供了一种简洁的方式来编写和使用函数。 + +闭包在 Rust 中非常灵活,可以存储在变量中、作为参数传递,甚至作为返回值。 + +闭包通常用于需要短小的自定义逻辑的场景,例如迭代器、回调函数等。 + +## 闭包与函数的区别 + +| 特性 | 闭包 | 函数 | +| :------------- | :------------------------- | :--------------- | +| **匿名性** | 是匿名的,可存储为变量 | 有固定名称 | +| **环境捕获** | 可以捕获外部变量 | 不能捕获外部变量 | +| **定义方式** | ` | 参数 | +| **类型推导** | 参数和返回值类型可以推导 | 必须显式指定 | +| **存储与传递** | 可以作为变量、参数、返回值 | 同样支持 | + +以下是 Rust 闭包的一些关键特性和用法: + +## 闭包的声明 + +闭包的语法声明: + +```rust +let closure_name = |参数列表| 表达式或语句块; +``` + +参数可以有类型注解,也可以省略,Rust 编译器会根据上下文推断它们。 + +```rust +let add_one = |x: i32| x + 1; +``` + +**闭包的参数和返回值:** 闭包可以有零个或多个参数,并且可以返回一个值。 + +```rust +let calculate = |a, b, c| a * b + c; +``` + +**闭包的调用:**闭包可以像函数一样被调用。 + +```rust +let result = calculate(1, 2, 3); +``` + +## 匿名函数 + +闭包在 Rust 中类似于匿名函数,可以在代码中以 **{}** 语法块的形式定义,使用 **||** 符号来表示参数列表,实例如下: + +```rust +let add = |a, b| a + b; +println!("{}", add(2, 3)); // 输出: 5 +``` + +在这个示例中,add 是一个闭包,接受两个参数 a 和 b,返回它们的和。 + +## 捕获外部变量 + +闭包可以捕获周围环境中的变量,这意味着它可以访问定义闭包时所在作用域中的变量。例如: + +```rust +let x = 5; +let square = |num| num * x; +println!("{}", square(3)); // 输出: 15 +``` + +以上代码中,闭包 square 捕获了外部变量 x,并在闭包体中使用了它。 + +闭包可以通过三种方式捕获外部变量: + +- **按引用捕获**(默认行为,类似 `&T`) +- **按值捕获**(类似 `T`) +- **可变借用捕获**(类似 `&mut T`) + +```rust +fn main() { + let mut num = 5; + + // 按引用捕获 + let print_num = || println!("num = {}", num); + print_num(); // 输出: num = 5 + + // 按值捕获 + let take_num = move || println!("num taken = {}", num); + take_num(); // 输出: num taken = 5 + // println!("{}", num); // 若取消注释,将报错,num 所有权被转移 + + // 可变借用捕获 + let mut change_num = || num += 1; + change_num(); + println!("num after closure = {}", num); // 输出: num after closure = 6 +} +``` + +**说明:** + +- 闭包默认按引用捕获外部变量。 +- 使用 `move` 关键字可以强制按值捕获,将外部变量的所有权转移到闭包内。 +- 如果闭包需要修改外部变量,需显式声明为 `mut` 闭包。 + +## 移动与借用 + +闭包可以通过 **move** 关键字获取外部变量的所有权,或者通过借用的方式获取外部变量的引用。例如: + +**借用变量:**默认情况下,闭包会借用它捕获的环境中的变量,这意味着闭包可以使用这些变量,但不能改变它们的所有权。这种情况下,闭包和外部作用域都可以使用这些变量。例如: + +```rust +let x = 10; +let add_x = |y| x + y; +println!("{}", add_x(5)); // 输出 15 +println!("{}", x); // 仍然可以使用 x +``` + +**获取所有权:**通过在闭包前添加 move 关键字,闭包会获取它捕获的环境变量的所有权。这意味着这些变量的所有权会从外部作用域转移到闭包内部,外部作用域将无法再使用这些变量。例如: + +```rust +let s = String::from("hello"); +let print_s = move || println!("{}", s); +print_s(); // 输出 "hello" +// println!("{}", s); // 这行代码将会报错,因为 s 的所有权已经被转移给了闭包 +``` + +通过这两种方式,Rust 提供了灵活的机制来处理闭包与外部变量之间的关系,使得在编写并发、安全的代码时更加方便。 + +## 闭包的特性 + +### 闭包可以作为函数参数 + +闭包经常作为参数传递给函数,例如迭代器的 .map()、.filter() 方法: + +```rust +fn apply_to_value(val: i32, f: F) -> i32 +where + F: Fn(i32) -> i32, +{ + f(val) +} + +fn main() { + let double = |x| x * 2; + let result = apply_to_value(5, double); + println!("Result: {}", result); // 输出: Result: 10 +} +``` + +这里的 Fn 是闭包的一个特性(trait),用于表示闭包可以被调用。 + +### 闭包可以作为返回值 + +闭包还可以作为函数的返回值。由于闭包是匿名的,我们需要使用 impl Trait 或 Box 来描述其类型。 + +使用 impl Fn 返回闭包 + +```rust +fn make_adder(x: i32) -> impl Fn(i32) -> i32 { + move |y| x + y +} + +fn main() { + let add_five = make_adder(5); + println!("5 + 3 = {}", add_five(3)); // 输出: 5 + 3 = 8 +} +``` + +使用 `Box` 返回闭包 + +```rust +fn make_adder(x: i32) -> Box i32> { + Box::new(move |y| x + y) +} + +fn main() { + let add_ten = make_adder(10); + println!("10 + 2 = {}", add_ten(2)); // 输出: 10 + 2 = 12 +} +``` + +### 闭包特性(Traits) + +闭包根据其捕获方式自动实现了以下三个特性: + +- **`Fn`**: 不需要修改捕获的变量,闭包可以多次调用。 +- **`FnMut`**: 需要修改捕获的变量,闭包可以多次调用。 +- **`FnOnce`**: 只需要捕获所有权,闭包只能调用一次。 + +```rust +fn call_closure(f: F) +where + F: FnOnce(), +{ + f(); // 只调用一次 +} + +fn main() { + let name = String::from("Rust"); + + // 使用 move 强制捕获所有权 + let print_name = move || println!("Hello, {}!", name); + + call_closure(print_name); + // println!("{}", name); // 若取消注释,将报错,name 的所有权已被移动 +} +``` + + + # Rust 所有权 计算机程序必须在运行时管理它们所使用的内存资源。 @@ -699,3 +1155,126 @@ fn makes_copy(some_integer: i32) { ``` 如果将变量当作参数传入函数,那么它和移动的效果是一样的。 + +### 函数返回值的所有权机制 + +```rust +fn main() { + let s1 = gives_ownership(); + // gives_ownership 移动它的返回值到 s1 + + let s2 = String::from("hello"); + // s2 被声明有效 + + let s3 = takes_and_gives_back(s2); + // s2 被当作参数移动, s3 获得返回值所有权 +} // s3 无效被释放, s2 被移动, s1 无效被释放. + +fn gives_ownership() -> String { + let some_string = String::from("hello"); + // some_string 被声明有效 + + return some_string; + // some_string 被当作返回值移动出函数 +} + +fn takes_and_gives_back(a_string: String) -> String { + // a_string 被声明有效 + + a_string // a_string 被当作返回值移出函数 +} +``` + +## 引用与租借 + +引用(Reference)是 C++ 开发者较为熟悉的概念。 + +如果你熟悉指针的概念,你可以把它看作一种指针。 + +实质上"引用"是变量的间接访问方式。 + +```rust +fn main() { + let s1 = String::from("hello"); + let s2 = &s1; + println!("s1 is {}, s2 is {}", s1, s2); +} +``` + +运行结果: + +``` +s1 is hello, s2 is hello +``` + +**&** 运算符可以取变量的"引用"。 + +当一个变量的值被引用时,变量本身不会被认定无效。因为"引用"并没有在栈中复制变量的值: + +![img](https://www.runoob.com/wp-content/uploads/2020/04/F25111E7-C5D3-464A-805D-D2186A30C8A0.jpg) + +函数参数传递的道理一样: + +```rust +fn main() { + let s1 = String::from("hello"); + + let len = calculate_length(&s1); + + println!("The length of '{}' is {}.", s1, len); +} + +fn calculate_length(s: &String) -> usize { + s.len() +} +``` + +运行结果: + +``` +The length of 'hello' is 5. +``` + +引用不会获得值的所有权。 + +引用只能租借(Borrow)值的所有权。 + +引用本身也是一个类型并具有一个值,这个值记录的是别的值所在的位置,但引用不具有所指值的所有权: + +```rust +fn main() { + let s1 = String::from("hello"); + let s2 = &s1; + let s3 = s1; + println!("{}", s2); +} +``` + +这段程序不正确:因为 s2 租借的 s1 已经将所有权移动到 s3,所以 s2 将无法继续租借使用 s1 的所有权。如果需要使用 s2 使用该值,必须重新租借: + +```rust +fn main() { + let s1 = String::from("hello"); + let mut s2 = &s1; + let s3 = s1; + s2 = &s3; // 重新从 s3 租借所有权 + println!("{}", s2); +} +``` + +这段程序是正确的。 + +既然引用不具有所有权,即使它租借了所有权,它也只享有使用权(这跟租房子是一个道理)。 + +如果尝试利用租借来的权利来修改数据会被阻止: + +```rust +fn main() { + let s1 = String::from("run"); + let s2 = &s1; + println!("{}", s2); + s2.push_str("oob"); // 错误,禁止修改租借的值 + println!("{}", s2); +} +``` +