高低电平

由于电源从正极到负极有电压差,并且电路是导通的,所以就会有电流通过,LED 小灯因为有了电流通过,所以就会直接发光。我们把右侧的原 GND 处接到单片机 P0.0 引脚上,那么如果我们单片机输出一个低电平,也就是跟 GND 一样的 0V 电压,就可以让 LED 小灯和图 2-4 一样发光了。

因为我们的单片机是可以编程控制的,我们可以让 P0.0 这个引脚输出一个高电平,就是跟 VCC 一样的 5V 电压,那么这个时候,左侧 VCC 电压和右侧的 P0.0 的电压是一致的,那就没有电压差,没有电压差就不会产生电流,没有电流 LED 小灯就不会亮,也就是会处于熄灭状态。

特殊功能寄存器和位定义

第一条语句是:sfr  P0 = 0x80;

sfr 这个关键字,是 51 单片机特有的,他的作用是定义一个单片机特殊功能寄存器(special function register)。51 单片机内部有很多个小模块,每个模块居住在拥有唯一房间号的房间内,同时每个模块都有 8 个控制开关。P0 就是一个功能模块,就住在了 0x80 这个房间里,我们就是通过设置 P0 内部这个模块的 8 个开关,来让单片机的 P0 这 8 个 IO 口输出高电平或者低电平的。而 51 单片机内部有很多寄存器,如果我们想使用的话必须提前进行 sfr 声明。不过 Keil 软件已经把所有这些声明都预先写好并保存到一个专门的文件中去了,我们要用的话只要文件开头添加一行#include<reg52.h>即可

第二条语句是:sbit  LED = P0^0;

这个 sbit,就是对刚才所说的 SFR 里边的 8 个开关其中的一个进行定义。经过上边第二条语句后,以后只要在程序里写 LED,就代表了 P0.0 口(“^”这个符号在数字键 6 上边),注意这个 P 必须大写,也就是说我们给 P0.0 又取了一个更形象的名字叫做 LED。
![[IO口特殊功能寄存器.png]]t 图中 P0 口所在的地址是 0x80,一共有从 7 到 0 这 8 个 IO 口控制位,后边有个 Reset Value(复位值),这个很重要,是我们看寄存器必看的一个参数,8 个控制位复位值全部都是 1。

这就是告诉我们,每当单片机上电复位的时候,所有的引脚的值默认是都是 1,即高电平,我们在设计电路的时候也要充分的考虑这个问题。

C 运算符

按位与 – &

只有当两个对应的二进制位都为 1 时,结果才为 1,否则为 0

按位或 – |

只有当两个对应的二进制位其中有一个是 1,结果就是 1,同 0 才为 0

按位异或 – ^

只有当两个对应的二进制位互异,结果就为 1,否则为 0

在 Rust 中,​​ 特征(Trait)​​ 就像是类型之间的魔法契约。它定义了一组可以被共享的行为能力 —— 只要某个类型签署(实现)了这个契约,它就获得了使用这些超能力的资格。

1
2
3
fn add<T: std::ops::Add<Output = T>>(a: T, b: T) -> T {
a + b
}

这个简单的例子中,add()函数对参数T的要求是:​​ 你必须会加法运算 ​​(实现了std::ops::Add特征)。特征就是这样约束类型的行为边界。

孤儿规则

特征实现有个关键限制:​​ 你要为类型 A 实现特征 T,那么 A 或 T 至少有一个是在你的代码宇宙(当前作用域)中定义的!​

这个规则被戏称为“孤儿规则”,它像是 Rust 的宇宙法则:

  • ✅ 可以为你的Dog结构体实现标准库的Display特征(Display来自外部宇宙)
  • ✅ 可以为标准库的String类型实现你的bark()特征(bark()来自你的宇宙)
  • ❌ ​​ 不能 ​​ 为String实现Display(两者都来自标准库宇宙)

这条规则保护了代码宇宙的和平:既防止外部代码破坏你的世界,也避免你无意中干扰其他宇宙的运转。

Trait Bound(特征约束):设置入场门槛

特征约束是泛型函数的保镖,它确保传入的值具备所需能力:

1
2
3
4
5
6
7
8
9
// 方式一:直接在泛型参数中设置约束
fn print_item<T: Display + Debug>(item: &T) {
println!("{:?}", item);
}

// 方式二:语法糖(更简洁,适合单个参数)
fn print_item_sugar(item: &(impl Display + Debug)) {
println!("{:?}", item);
}

当约束变复杂时,where子句能让代码更清晰:

1
2
3
4
5
6
7
8
9
10
// 老写法:约束混杂在泛型声明中
fn complex_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) {}

// 新写法:where 子句让约束一目了然
fn clean_function<T, U>(t: &T, u: &U) -> i32
where T: Display + Clone, // T 必须会显示和克隆
U: Clone + Debug // U 必须可克隆和调试
{
// ...函数逻辑
}

用特征约束实现条件方法

特征约束可以给泛型结构体添加特定条件下的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct Pair<T> {
x: T,
y: T,
}

// 所有 Pair 都有的方法
impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self { x, y }
}
}

// 只有具备显示和比较能力的 Pair 才有此方法
impl<T: Display + PartialOrd> Pair<T> {
fn show_winner(&self) {
if self.x >= self.y {
println!("冠军是 x: {}", self.x);
} else {
println!("冠军是 y: {}", self.y);
}
}
}

这样写既保持了代码的条理性,又精确控制了方法的可用范围。

特征对象:运行时的多态魔法

当需要返回多种类型时,impl Trait的静态方式会失效:

1
2
3
4
5
6
7
8
// 编译错误:if 和 else 分支返回了不同类
fn select_pet(is_dog: bool) -> impl Animal {
if is_dog {
Dog::new("Buddy")
} else {
Cat::new("Whiskers")
}
}

这时需要 ​​ 特征对象 ​​ —— 一种运行时多态机制。它通过动态分发,让一个指针能代表多种具体类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 使用 Box 在堆上分配
fn select_pet(is_dog: bool) -> Box<dyn Animal> {
if is_dog {
Box::new(Dog::new("Buddy"))
} else {
Box::new(Cat::new("Whiskers"))
}
}

// 使用引用(需注意生命周期)
fn borrow_pet<'a>(is_dog: bool) -> &'a dyn Animal {
if is_dog {
&Dog::new("Buddy")
} else {
&Cat::new("Whiskers")
}
}

特征对象的注意事项

  1. ​ 大小很重要:​
    特征对象大小在编译期不确定,必须通过指针使用:
1
2
3
4
5
// 错误:dyn Animal 大小未知
fn receive_animal(animal: dyn Animal) {}

// 正确:通过指针传递
fn receive_animal_smart(animal: &dyn Animal) {}
  1. ​ 能力限制:​
    特征对象只能调用特征中定义的方法:
1
2
3
let dog: Box<dyn Animal> = Box::new(Dog::new("Buddy"));
dog.walk(); // ✅ 可以调用
dog.bark(); // ❌ 错误:Animal 特征没有 bark 方法

标记特征:类型的能力徽章

标记特征是给类型颁发的特殊能力徽章,通常不包含具体行为,只声明某种能力:

1
2
3
4
5
// 定义高级特征,要求类型具备调试、相等和默认能力
trait Premium: Debug + PartialEq + Default {}

// 为自定义类型颁发Premium徽章
impl Premium for MyStruct {}

在函数约束中使用:

1
2
3
fn premium_function<T: Premium>(item: T) {
// 这里可以安全使用Debug、PartialEq和Default方法
}

这样避免了重复书写多个特征约束,让代码更简洁。

关联类型:特征中的类型占位符

当特征需要返回不同类型时,关联类型提供了优雅的解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
trait TravelTime {
type Destination; // 占位符类型

fn travel(&self) -> Self::Destination; // 返回占位符类型
}

impl TravelTime for Car {
type Destination = City; // 实际指定为City

fn travel(&self) -> City {
City::new("北京")
}
}

impl TravelTime for Spaceship {
type Destination = Planet; // 实际指定为Planet

fn travel(&self) -> Planet {
Planet::new("火星")
}
}

关联类型 vs 泛型参数

场景 解决方案 代码示例
单一类型映射 关联类型 type Output;
需要多种类型组合 泛型参数 trait Add<Rhs>
需要多个实现 泛型参数 impl Add<i32>impl Add<Point>
保持实现简洁 关联类型 避免impl Add<Point, Point>的复杂性

特征 vs 泛型:静态与动态的舞蹈

特性 泛型 特征对象
分发方式 静态分发(编译期) 动态分发(运行时)
性能 零开销,直接调用具体方法 有运行时查找的小开销
二进制大小 可能较大(为每个类型生成副本) 较小
灵活性 只能返回一种具体类型 可返回多种类型
适用场景 性能敏感代码,单一类型处理 需要运行时多态的复杂场景

​ 简单来说:​

  • 泛型像是编译器帮你定制专用工具,高效但缺乏灵活性
  • 特征对象像是万能工具箱,灵活但需要额外操作成本

注意:该文章由 DeepSeek R1 结合课程笔记优化生成,并由 Garusuta 修改发布

Rust 最独特也最强大的特性之一就是它的 ​​ 所有权系统 ​​。这套系统在编译期就能帮你杜绝一大类内存错误,比如数据竞争和悬垂指针(引用指向了无效的内存)。理解所有权和借用规则是掌握 Rust 的关键。它们的核心思想其实挺简单的:


所有权规则 (Ownership Rules)

想象每个值都有一个“主人”。这套规则规定了主人和值的关系:

  1. ​ 每个值都有自己的主人:​​ 在 Rust 中,每一个值都必须被一个变量所拥有,这个变量就是它的 ​​ 所有者 ​​。
  2. ​ 一次只能有一个主人:​​ 一个值在同一个时间点,只能被一个变量所拥有。也就是说,​​ 一个值只有一个所有者 ​​。
  3. ​ 主人走了,值就没了:​​ 当作为所有者的 ​​ 变量离开它的作用域 ​​ 时,Rust 会自动调用 drop 函数,把这个值占用的内存清理掉(这个过程叫 ​​drop​​)。这保证了内存不会泄露。

借用规则 (Borrowing Rules)

直接转移所有权(比如通过函数传参)有时候不方便。Rust 提供了“借用”机制,让你能 ​​ 临时访问 ​​ 一个值,而不需要拿走所有权。借用是通过 ​​ 引用 ​​ (&) 和 ​​ 可变引用 ​​ (&mut) 来实现的。规则也很清晰:

  1. ​ 借东西也要讲规矩:​
    • 在 ​​ 任何给定时刻 ​​,对于同一块数据,你只能选择以下 ​​ 一种 ​​ 方式借用:
      • ​ 要么 ​​ 拥有 ​​ 一个 ​​ 可变的引用 (&mut)。
      • ​ 要么 ​​ 拥有 ​​ 任意多个 ​​ 不可变的引用 (&)。
    • 你不能同时有可变引用和不可变引用存在,也不能有多个可变引用同时活跃。(官方总结得非常到位:​​ 同一时刻,你只能拥有要么一个可变引用,要么任意多个不可变引用 ​)。
  2. ​ 借的东西必须有效:​​ 所有 ​​ 引用必须始终指向有效的内存 ​​。你不能拥有一个指向已经被释放的数据的引用(悬垂引用)。(官方原则:​​ 引用必须总是有效的 ​)。

为什么要这么设计?(内存安全!)

这些规则的核心目标就是为了解决两个臭名昭著的内存安全问题:

  • ​ 数据竞争 (Data Races):​​ 当多个线程同时访问同一块数据,且至少有一个线程在写,又没有同步机制时,就会发生数据竞争。结果可能是程序崩溃、数据损坏,或者出现完全预料之外的行为。Rust 的借用规则(规则 1)在编译期就几乎完全杜绝了数据竞争的可能性(在单线程上下文中,它也能防止意外的并发访问模式)。
  • ​ 悬垂引用 (Dangling References):​​ 引用指向的内存可能已经被释放了。访问这样的引用就像在悬崖边走钢丝,程序随时可能崩溃(Segmentation Fault)。所有权规则(规则 3)和借用规则(规则 2)共同确保了引用指向的数据在引用存活期间一定是有效的。

规则是如何防止问题的?看例子!

例子 1:违反规则 - 多个可变引用 (数据竞争风险)

1
2
3
fn main() {
let mut vec = vec;
}
  • ​ 问题:​​ 这段代码违反了借用规则 1:不能同时拥有多个活跃的可变引用 (ref1ref2 都想独占 vec)。
  • ​ 编译器会报错:​​ Rust 编译器会阻止你编译这段代码。
  • ​ 修复:​​ 通常需要调整代码结构,确保同一时间只有一个可变引用在使用。比如,去掉其中一个 println! 中对 ref1ref2 的使用。

例子 2:违反规则 - 同时借用可变和不可变 (数据竞争风险)

1
2
3
4
fn main() {
let mut vec = vec 存活时尝试借可变引用
println!("ref1: {:?}, ref2: {:?}", ref1, ref2);
}
  • ​ 问题:​​ 违反了借用规则 1:不能同时拥有活跃的不可变引用 (ref1) 和可变引用 (ref2)。为什么不行?
    • 如果有人通过 ref2(可变引用)修改了 vec(比如 ref2.push(7)),那么 ref1(不可变引用)读取到的值就变得不可预测了,这违背了不可变引用“只读”的承诺。
    • 官方文档解释得很形象:​​ 正在借用不可变引用的用户,肯定不希望他借用的东西,被另外一个人莫名其妙改变了。多个不可变借用被允许是因为没有人会去试图修改数据,每个人都只读这一份数据而不做修改,因此不用担心数据被污染。​
  • ​ 编译器会报错:​​ 同样编译不过。

例子 3:作用域的改变 (编译器越来越聪明了!)

早期版本的 Rust 编译器(Rust 1.31 之前)中,引用的作用域和它绑定的变量作用域是一样的(到花括号 } 结束)。这有时候写起来挺别扭:

1
2
3
4
5
6
7
8
9
10
11
fn main() {
let mut s = String::from("hello");

let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2); // 在这里,r1 和 r2 的使命完成了
// 在早期编译器看来,r1 和 r2 的作用域要持续到 main 函数结束的花括号

let r3 = &mut s; // 早期编译器:错误!认为 r1 和 r2 (不可变) 还活着,不能借可变
println!("{}", r3);
} // 早期编译器:认为 r1, r2, r3 作用域在这里结束
  • ​ 老问题:​​ 在老编译器眼里,r1r2 的作用域一直到 main 函数结尾的 },所以在 println! 之后尝试创建可变引用 r3 会报错(违反了规则 1:同时存在不可变和可变借用)。
  • ​ 新改进:​​ 现代 Rust 编译器(引入了 ​​Non-Lexical Lifetimes, NLL​​)变得更聪明了!它 ​​ 判断引用的作用域结束于它最后一次被使用的位置 ​​(last use)。在上面的代码中:
    • r1r2println!("{} and {}", r1, r2); 之后就不再被使用了。
    • 编译器认为它们的借用作用域 ​​ 到此结束 ​​。
    • 因此,紧接着创建可变引用 r3 是允许的,代码能顺利编译通过。
  • ​ 新作用域:​
1
2
3
4
5
6
7
8
9
10
fn main() {
let mut s = String::from("hello");

let r1 = &s;
let r2 = &s;
println!("{} and {}", r1, r2); // r1, r2 的借用作用域在此结束 (NLL)

let r3 = &mut s; // 允许!因为 r1, r2 的借用已经结束了
println!("{}", r3); // r3 的借用作用域在此结束 (NLL)
}

​ 总结:​​ Rust 的所有权和借用规则初看可能有点严格,但它们正是 Rust 实现内存安全并发且无需垃圾回收的核心武器。编译器是你的好帮手,它会严格执行这些规则。理解并习惯这些规则后,你会发现在它们约束下写出的代码,天然就具备更高的安全性和可靠性。


注意:该文章由 DeepSeek R1 结合课程笔记优化生成,并由 Garusuta 修改发布

好的,我们来详细解释一下 Rust 中的 into_iter(), iter(), 和 iter_mut() 这三个方法。它们都是用于从集合(如 Vec, HashMap, String, 数组等)创建迭代器的核心方法,但它们在处理集合 ​​ 所有权 ​​ 和 ​​ 元素访问权限 ​​ 上有本质区别。

​ 核心区别总结:​

方法 所有权转移 元素访问方式 返回的迭代器类型 集合之后是否可用
into_iter() ​ 消耗 ​​ 集合 (Move) ​ 拥有 ​​ 元素 (T) IntoIterator::Item = T ​ 不可用 ​
iter() ​ 借用 ​​ 集合 (Immutable Borrow) ​ 不可变引用 ​​ (&T) Iterator::Item = &T ​ 可用 ​
iter_mut() ​ 可变借用 ​​ 集合 (Mutable Borrow) ​ 可变引用 ​​ (&mut T) Iterator::Item = &mut T ​ 可用 ​

​ 详细解释:​

  1. into_iter() (Into Iterator)​

    • ​ 所有权:​​ 这个方法 ​​ 消耗 ​​(consume)集合本身。调用 into_iter() 后,​​ 原始集合的所有权被转移 ​​ 到迭代器中。这意味着你不能再使用原来的集合变量(除非它实现了 Copy trait,但集合类型通常不会)。
    • ​ 元素访问:​​ 迭代器 ​​ 产出集合中每个元素本身 (T)​​。你获得了元素的所有权。
    • ​ 用途:​
      • 当你 ​​ 不再需要原始集合 ​​,并且想逐个 ​​ 拥有 ​​ 集合中的元素进行处理时。
      • for 循环中直接使用集合变量时,Rust 会 ​​ 隐式调用 into_iter()​。例如:
      1
      2
      3
      4
      5
      let v = vec![1, 2, 3];
      for num in v { // 等价于 `for num in v.into_iter()`
      println!("{}", num);
      }
      // println!("{:?}", v); // 错误!v 的所有权已被移动
      • 需要将集合 ​​ 转换 ​​ 成另一种类型时(例如 Vec<T> 转换成 Vec<U>)。
    • ​ 返回值:​​ 一个迭代器,其 Item 类型是 T(元素类型本身)。
  2. iter() (Iterator)​

    • ​ 所有权:​​ 这个方法对集合进行 ​​ 不可变借用 ​​。调用 iter() 后,你仍然可以继续使用原始集合。
    • ​ 元素访问:​​ 迭代器 ​​ 产出集合中每个元素的不可变引用 (&T)​​。你可以读取元素的值,但不能修改它们。
    • ​ 用途:​
      • 当你只需要 ​​ 读取 ​​ 集合中的所有元素,而不需要修改它们或集合本身时。
      • 需要多次遍历同一个集合时。
      • for 循环中显式使用 iter() 来避免移动所有权:
        1
        2
        3
        4
        5
        let v = vec![1, 2, 3];
        for num in v.iter() { // num 是 &i32
        println!("{}", num);
        }
        println!("{:?}", v); // 正确,v 仍然可用
    • ​ 返回值:​​ 一个迭代器,其 Item 类型是 &T(元素的不可变引用)。
  3. iter_mut() (Mutable Iterator)​

    • ​ 所有权:​​ 这个方法对集合进行 ​​ 可变借用 ​​。调用 iter_mut() 后,在迭代器存活期间,你 ​​ 不能 ​​ 再通过原始变量或其他方式 ​​ 可变地访问 ​​ 这个集合(Rust 的借用规则确保只有一个可变引用存在)。但是,你仍然可以在迭代完成后使用集合。
    • ​ 元素访问:​​ 迭代器 ​​ 产出集合中每个元素的可变引用 (&mut T)​​。你可以 ​​ 读取并修改 ​​ 元素的值。
    • ​ 用途:​
      • 当你需要 ​​ 修改 ​​ 集合中的元素时。
      • 例如:
        1
        2
        3
        4
        5
        let mut v = vec![1, 2, 3];
        for num in v.iter_mut() { // num 是 &mut i32
        *num *= 2; // 解引用并修改值
        }
        println!("{:?}", v); // 输出: [2, 4, 6]
    • ​ 返回值:​​ 一个迭代器,其 Item 类型是 &mut T(元素的可变引用)。

​ 关键点记忆:​

  • into_iter():​​ “我全都要!” - 拿走整个集合和里面所有东西的所有权。原集合消失。
  • iter():​​ “借我看看!” - 只读借用集合和里面的东西。原集合完好无损,可以继续用。
  • iter_mut():​​ “借我改改!” - 可修改借用集合里的东西。原集合结构还在,但里面的内容可能变了。

​ 选择哪个?​

  • 想 ​​ 修改元素 ​​?用 iter_mut()
  • 只想 ​​ 读取元素 ​​ 且 ​​ 保留集合 ​​?用 iter()
  • 想 ​​ 消费集合 ​​(比如转换元素类型、把集合所有权传给其他函数)?用 into_iter()(或直接在 for 循环里用集合变量)。

注意:该文章由 DeepSeek R1 结合课程笔记优化生成,并由 Garusuta 修改发布

在 Rust 中,FnFnOnceFnMut 是定义闭包行为的三个核心 trait,它们决定了闭包如何捕获环境变量以及如何被调用。以下是清晰的解释和对比:


​1. 核心概念 ​

Trait 所有权要求 调用方式 典型用途
FnOnce 获取所有权(self 只能调用 ​​ 一次 ​ 消耗资源的闭包(如移动所有权)
FnMut 可变借用(&mut self 可调用 ​​ 多次 ​​(可修改状态) 需要修改捕获变量的闭包
Fn 不可变借用(&self 可调用 ​​ 多次 ​​(不可修改状态) 只读取捕获变量的闭包

​2. 详细解释 ​

​(1) FnOnce

  • ​ 所有权 ​​:通过值(self)捕获变量,​​ 消耗 ​​ 闭包自身。
  • ​ 调用限制 ​​:​​ 只能调用一次 ​​(调用后闭包失效)。
  • ​ 使用场景 ​​:需要转移所有权的操作(如 std::thread::spawn)。
  • ​ 示例 ​​:
1
2
3
4
5
6
7
let s = String::from("hello");
let consume = || {
println!("{}", s); // 移动所有权
std::mem::drop(s); // 显式消耗变量
};
consume(); // 第一次调用(成功)
// consume(); // 错误!闭包已失效

​(2) FnMut

  • ​ 所有权 ​​:通过可变引用(&mut self)捕获变量,​​ 可修改 ​​ 环境。
  • ​ 调用限制 ​​:​​ 可多次调用 ​​,每次调用可能改变状态。
  • ​ 使用场景 ​​:迭代器适配器(如 filtermap)中需要修改状态的闭包。
  • ​ 示例 ​​:
1
2
3
4
5
6
7
let mut count = 0;
let mut increment = || {
count += 1; // 修改捕获的变量
println!("Count: {}", count);
};
increment(); // Count: 1
increment(); // Count: 2(状态被修改)

​(3) Fn

  • ​ 所有权 ​​:通过不可变引用(&self)捕获变量,​​ 只读 ​​ 访问环境。
  • ​ 调用限制 ​​:​​ 可多次调用 ​​,且不改变闭包状态。
  • ​ 使用场景 ​​:无需修改环境的操作(如事件处理器)。
  • ​ 示例 ​​:
1
2
3
4
5
let s = String::from("hello");
let print = || println!("{}", s); // 只读借用
print(); // "hello"
print(); // "hello"(状态不变)
println!("{}", s); // 外部仍可访问(未移动)

​3. 继承关系 ​

1
2
3
graph LR
FnOnce --> FnMut
FnMut --> Fn
  • ​ 规则 ​​:
    • 所有闭包都至少实现 FnOnce(因为每个闭包至少会被调用一次)。
    • 实现 Fn 的闭包自动实现 FnMutFnOnce
    • 实现 FnMut 的闭包自动实现 FnOnce

​5. 实际代码示例 ​

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
32
33
34
35
// 接受 FnOnce 的函数
fn run_once<F: FnOnce()>(f: F) {
f();
}

// 接受 FnMut 的函数
fn run_mut<F: FnMut()>(mut f: F) {
f();
f(); // 可多次调用
}

// 接受 Fn 的函数
fn run<F: Fn()>(f: F) {
f();
f(); // 可多次调用
}

fn main() {
let s = String::from("hello");

// FnOnce 示例
run_once(|| {
println!("{}", s); // 移动所有权
// drop(s); // 若显式消耗,闭包只能调用一次
});

// FnMut 示例
run_mut(|| {
// 无法修改 s(未声明 mut),但可调用多次
println!("{}", s);
});

// Fn 示例
run(|| println!("{}", s)); // 只读借用
}

​6. 关键总结 ​

  • FnOnce​:用于一次性闭包,消耗所有权。
  • FnMut​:用于需修改状态的闭包,可多次调用。
  • Fn​:用于只读闭包,可安全多次调用。
  • ​ 编译器自动推断 ​​:闭包实现哪些 trait 取决于 ​​ 如何使用捕获变量 ​​(而非如何声明)。

注意:该文章由 DeepSeek R1 结合课程笔记优化生成,并由 Garusuta 修改发布

0%