本文基于 清华邓博士的 B 站视频 整理,时长约 2 小时 55 分钟。
零、这堂课在讲什么
这是”Golden Rust”系列的第一讲。它不讲具体的 Rust 语法细节,而是建地基——解释 Rust 为什么是这样设计的。核心回答三个问题:
- Rust 在嵌入式领域为什么有前途?(生态优势 > 安全优势)
- Rust 的 trait 系统解决了 C/C++ 的什么根本问题?(组合 vs 继承)
- 理解 Rust 需要哪些底层认知?(栈/堆/内存布局/编译器哲学)
如果你刚接触 Rust,这堂课会帮你建立一个”为什么这么学”的框架。如果你写 C/C++,后半部分对栈和堆的解释会让你重新审视自己每天都在用的概念。
一、Rust 的三个核心优势(为什么它能进入嵌入式?)
传统认知是:“Rust = 内存安全语言”。但在这堂课中,主讲人把安全排在了第三位。他给出的排序是:
高性能 > 抽象能力 > 内存安全
1.1 高性能:零运行时,像 C 一样快
嵌入式系统最底层是裸机——没有操作系统那一层缓冲。你的代码直接跑在硬件上。这意味着什么?
┌─────────────────────────┐│ Python / JavaScript │ ← 有运行时/GC,嵌入式无法承受├─────────────────────────┤│ C++ │ ← 有运行时开销(虚表/异常/RTTI)├─────────────────────────┤│ C / Rust │ ← 零运行时,编译后几乎一对一映射为机器指令└─────────────────────────┘“嵌入式中间没有那么多 OS 层,全是裸机。所以必须要一个像 C 语言一样快的东西,小的运行时,基本上就是你写的代码叠加一点点东西就能够跑。”
Rust 和 C 在这里站在同一条起跑线上。C++ 则因为虚函数表、异常处理、RTTI 等机制产生的额外运行时开销,在资源极端受限的嵌入式场景中几乎没有立足之地。
1.2 抽象能力:解决嵌入式生态的碎片化
这才是主讲人眼中 Rust 最闪亮的点。
嵌入式世界极度碎片化:STM32 的库和 ESP32 的库完全不兼容,8 位机和 32 位机的编程模型完全不一样。“不同的开发板,里面的芯片结构不一样,管脚不一样,IO 设备不一样,指令架构不一样——特别散。”
Rust 的 trait 系统提供了一种统一的抽象层。官方有专门的 Embedded Working Group 负责定义接口标准,然后:
┌──────────────────────────────────────┐│ 上层生态(15万个 crate) │ ← 依赖 trait 接口,不依赖具体硬件├──────────────────────────────────────┤│ 官方 trait 抽象层(embedded-hal 等) │ ← Rust Embedded WG 制定├──────────────────────────────────────┤│ 厂商实现:STM32 HAL │ ESP32 HAL │ nRF │ ← 各厂商/社区对接同一套 trait└──────────────────────────────────────┘“抽象一旦做好,官方给出的标准抽象做好,大家往上靠就完了。靠上了以后,大家可以互通有无了。”
他举了一个 C 语言中”从来不会发生”的例子:在 ESP32 嵌入式开发板上运行一个 Lisp 解释器。在 C 语言里,移植一个 Lisp 解释器到特定嵌入式平台要处理大量平台相关的依赖——而在 Rust 里,确认包支持 no_std,加一行 Cargo.toml 依赖,就能编译运行。
对比 C 语言:
“C 的很多生态的很多包都是各自为战的,无法融会贯通搞在一起。在 Rust 里面,各行各业的标准库都给好了,你直接用,一站式解决。“
1.3 内存安全:消除一整类 Bug
把未来的问题挪到编译期。这在后面第五节会详细展开。
二、为什么 C 无法形成这样的生态?
这是整堂课花了最长时间论证的核心命题。
2.1 根源:C 缺少”标准数据结构”
设想你要设计一个通用的 String 类型。随着使用者增多,以下问题会逐一浮现:
| 维度 | C 的做法 | Python 的做法 | Rust 的做法 |
|---|---|---|---|
| 用什么编码存储? | 没规定,自己定 | 统一用 4 字节/字符 | UTF-8(变长,节省空间) |
| 修改串是原地改还是生成新串? | 没规定 | 能 copy 就 copy(牺牲性能) | 两种都有(&str 只读,String 可写) |
| 分割函数返回什么容器? | 各自实现 | 返回新的 list | 返回迭代器(零拷贝) |
| 容器元素是副本还是引用? | 各自实现 | 副本 | 取决于你选 &str 还是 String |
“你表面上从逻辑上想用一个人类可用的字符串,在微观层面上如果真的要考虑内存,要考虑这么多事情。有巨量的细节需要考虑,而且很多问题没有通用解法。”
这就是组合爆炸:这些问题的每个维度都是正交的,组合起来产生指数级的可能性。C 语言直接放弃了——让每个库的作者自己决定。结果就是:
- 库 A 造了自己的 Vector
- 库 B 造了自己的 Vector
- 库 A 的 Vector 和库 B 的 Vector 不兼容
- 你要同时用 A 和 B,就得手写数据结构转换代码
“真正的让生态繁荣起来,你 A 做的包和 B 做的包要想接在一起,最重要的是数据结构表达的统一。大家操作的都是同样一套内存表达,这个事情才能玩在一起。”
Python 靠内置 list/dict/str 做到了这一点(所以生态庞大),但代价是性能(比 C 慢 300 倍,在嵌入式无法使用)。Rust 则通过内置标准数据结构 + trait 系统做到了同样的统一性,但零运行时开销。
2.2 Rust 的解法:组合爆炸解决组合爆炸
“Rust 是我见到的目前来说唯一一个直面这些问题的语言。如果我们在语言层面上就特别倾向于用组合爆炸的表达方式——很多小函数,每个小函数只做一小部分转换,然后串在一起形成组合——那么也许它就可以解决这些精细的组合爆炸的需求。”
这体现在 Rust 代码特有的链式调用风格:
// Rust 的典型风格:每一步都是一个小转换let result = values .iter() // 获取迭代器 .map(|x| x * 2) // 每个元素乘以2 .filter(|x| *x > 10) // 过滤 .collect::<Vec<_>>(); // 收集成 Vec每步都是一个独立的小操作,可以任意替换、组合。这种风格在 C 语言中需要用多次独立的函数调用和临时变量来表达,失去了简洁性。
三、Trait:用”维度”代替”继承”
3.1 香蕉与大猩猩
“面向对象的语言的问题在于,他总是附带着他所需要的隐含环境。你想要一个香蕉,但得到的是一个拿着香蕉的大猩猩,而其后还有整个丛林。”
这句话不是主讲人原创的——它出自 Joe Armstrong(Erlang 之父),收录于《Coders at Work》(2009年)。它精确地描述了继承的问题:当你从类 A 继承时,你继承了 A 的全部——所有成员变量、所有方法。你无法”只要香蕉”。
继承能很好地表达天然就是层级结构的关系(比如 GUI 组件:按钮是窗口的子类),但现实中大部分情况是:
“四不像——它又像猫,又像老虎。最佳的表达应该是把猫的一部分特性拿过来,把老虎的一部分特性拿过来,叠加起来是它。“
3.2 用维度理解 Trait
主讲人提出了一个非常直观的理解方式:
能看(维度) ▲ 人 ● │ ● 机器人 │ ───────────┼───────────→ 能跑(维度) │ │ ● 特斯拉汽车 │struct = 实体(真正占内存的东西)。 人、机器人、特斯拉汽车,它们在内存中可以完全不像。
trait = 维度(一种”能力标签”)。 “能看”是一个维度——人和机器人在这个维度上都在正象限。“能跑”是另一个维度——人和特斯拉在正象限,但普通机器人可能在负象限。
“生态本质是官方控制了 trait,提供了一堆 trait,大家底下随便换就行了。上面的生态去依赖 trait,不要依赖于具体的结构。这就是抽象的核心——做结构跟 trait 之间的依赖,而不是结构和结构之间的依赖。”
映射到编程语言:在 C++ 里,trait 最接近”只有纯虚函数的抽象类”——但这个类比很蹩脚。Rust 的 trait 远比 C++ 的纯虚类强大,它天生支持组合,不需要也不支持继承。
四、语言的三个横切面
“整个 Rust 语言就是三层结构。元能力、抽象能力、具象能力。没有第四层。它非常克制,不会乱加没用的特性。“
4.1 元能力(宏):自己定义语法
println! 后面那个感叹号 ! 代表它是一个宏。宏的本质是代码生成器——在编译期把你的简写展开成完整代码。
// 你写的println!("x = {}", x);
// 编译器展开后(大致相当于)io::_print(format_args!("x = {}\n", x));更强大的宏可以让你在 Rust 中嵌入其他语言的语法:
- Slint(23k+ stars):在 Rust 中嵌入声明式 UI 语法,编译为原生 GUI,支持树莓派/STM32 等嵌入式平台
- SQLx(17k+ stars):在 Rust 中直接写 SQL,编译期连接数据库验证语法正确性
// SQLx:原生 SQL,编译期验证let countries = sqlx::query!( "SELECT country, COUNT(*) as count FROM users GROUP BY country WHERE organization = ?", organization).fetch_all(&pool).await?;// countries[0].country, countries[0].count — 编译期就确定类型4.2 抽象能力(Trait/泛型)
这是第三部分讲的内容——trait 系统决定了 Rust 能有多强的表达力和生态潜力。
4.3 具象能力:类型与值
视频的后半段开始进入具体的类型系统。核心原则是:Rust 的类型必须精确,没有任何模糊空间。
基本数值类型:
在 C 语言里,int 到底是几个字节?C 标准只说 long ≥ int ≥ short,具体长度由编译器和平台决定。想精确控制就必须写 int32_t(还需要 #include <stdint.h>)。
Rust 直接把这个问题从源头消灭:
| 类型 | 含义 |
|---|---|
i8 / i16 / i32 / i64 / i128 | 有符号整数,位宽写在类型名里 |
u8 / u16 / u32 / u64 / u128 | 无符号整数 |
f32 / f64 | 浮点数 |
usize / isize | 和当前 CPU 架构宽度一致(32位机器=32位,64位机器=64位) |
char | 4 字节,完整覆盖 Unicode(包括 emoji) |
bool | true 或 false |
usize 的存在是因为有时你需要表达”和机器字长一样大的数字”——比如指针大小、数组索引、内存偏移量。如果你的代码要同时在 32 位和 64 位平台上跑,用 usize 而不是 u32 或 u64。
Rust char ≠ C char。 C 的 char 是 1 字节,只能存 ASCII。Rust 的 char 是 4 字节,存的是完整的 Unicode 标量值——中文、日文、emoji 全在里面,一个 char 一个字,没有乱码问题。主讲人的评价是:“他觉得这个代价是可以付得起的。”
数字字面量也可以很精确:
let x = 42; // 默认 i32let y = 42_i64; // 显式指定为 i64let z = 0xff_u8; // 十六进制 u8let big = 1_000_000; // 下划线分隔,跟写 1000000 完全一样下划线纯粹是为了人眼可读——三位一组,就像财务数字写逗号一样。
4.4 类型推导:看起来像脚本,实际是强类型
let x = 42; // 你没写类型,但编译器知道 x 是 i32“这种写法感觉上好像像咱们 Python 里面写脚本一样,你都不需要声明类型。但实际上是强类型语言——他后面都帮你填上了。”
这对应 C++ 的 auto,但 Rust 的类型推导能力更强。在 VS Code 里装了 rust-analyzer 插件后,把鼠标悬停在变量上就能看到推导出的类型——“相当于他帮你写了这个小代码。”
“原来你都得自己写这个,现在不用写了,让他自动帮你推导,这多香啊。”
Rust 官方有个网页版 playground(play.rust-lang.org),可以直接在浏览器里写代码运行。但主讲人推荐配本地 VS Code——“你写的程序一复杂以后,它的类型是什么?我跟你说,Rust 里面玩的就是类型。对类型要非常的敏感。他玩的就是类型,他把所有的信息全都堆到类型里面。“
4.5 宏再深入:println! 展开与卫生红
在第四部分的元能力基础上,视频后半段做了更具体的演示。
println! 是宏,不是函数。 你写 println!("x = {}", x),编译器第一步就是把这个宏展开。展开后的代码比原来长得多——println 本身消失了,变成了一串对标准库输出函数的调用。cargo expand 命令可以随时查看展开结果,“去掉红的魔法,看看它是什么。”
宏的 ! 后面可以跟三种括号:
println!("hello"); // 小括号——看起来像函数调用vec![1, 2, 3]; // 中括号——看起来像数组println!{"hello"}; // 大括号——看起来像代码块“你用什么完全取决于你想在这个地方表达什么。像函数一样调用就用小括号,像一段代码块就用大括号——这是自由的。”
卫生红(Hygienic Macro): “卫生”的意思是像函数一样——内部定义的变量不会”污染”外部作用域。println! 内部不管展开成什么样,都不会跟你外面的变量名冲突。这是 Rust 宏区别于 C 语言 #define 宏的根本特征——C 的宏只是文本替换,会产生各种意想不到的变量名冲突。
4.6 Rust 与 C 的关系:C 是后盾
“Rust 跟 C 的接口极其好。它可以自动从 C 头文件中生成 Rust 代码,你直接就可以用了。有 C 作为保底——如果有些能力官方只提供了 C 库,没法用 Rust 怎么办?自己手动生成一个就完了。”
这回答了”如果生态还不够怎么办”的问题——Rust 不会让你无路可走。它的 FFI(Foreign Function Interface)跟 C 的对接极其顺畅。
五、编译器的”安全区”模型
5.1 把 Bug 消灭在编译期
“Rust 把所有的潜在问题全都堆积在编译器。如果编译过了,你基本上大概率就成功了。他把未来的问题全都挪到现在——你在写代码的时候就要面对它们。”
┌────────────────────────────────┐│ 逻辑上正确的代码(理想边界) ││ ┌──────────────────────────┐ ││ │ Rust 编译器允许的"安全区" │ │ ← 宁可错杀,不放过│ │ (编译器通过的,全部正确) │ ││ └──────────────────────────┘ ││ ↑ 这之间的灰色地带 ││ 有些代码逻辑没问题, ││ 但编译器不让你写 │└────────────────────────────────┘代价是受限制——“我们写的时候需要更加谨慎才能过”。但好处是:
“你经过了这个语言严谨的训练以后,你再去写 C 和 C++,你能看到里面很多漏洞——‘哎,这个内存他怎么没考虑到?那个怎么没考虑到?‘有点入奢易,由奢入俭难。“
5.2 算术溢出:C vs Rust
一个小例子说明 Rust 的严谨性:
// C:默默截断,没有任何提示int16_t x = 300 * 300 * 300; // 溢出,静默截断为低 16 位// Rust:编译不通过!let x: i16 = 300 * 300 * 300; // error: attempt to multiply with overflow你必须显式告诉编译器你要怎么处理溢出:
let x: i16 = (300_i16).saturating_mul(300).saturating_mul(300); // 溢出就顶到头 32767saturating_* 是”饱和运算”的一种——溢出就取最大值。还有 wrapping_*(回绕到最小值继续算)、checked_*(返回 Option)等。这就是 Rust 的组合方式:每种溢出策略是一个独立的小函数,你想用哪种就链哪种。
类型转换同样必须显式。在 C/C++ 里,int8_t 传给需要 uint32_t 的函数会自动隐式转换——“默默的发生,你不知道。” Rust 不允许:
let y: i8 = 20;let x: u32 = y; // 编译错误!i8 不能自动变 u32let x: u32 = y as u32; // 显式转换,用 as 关键字“在 Rust 里面很多地方不能囫囵吞枣。你说多少就是多少。”
as是 Rust 的关键字,用于显式类型转换——和saturating_add等运算级转换互补。
六、栈与堆
6.1 为什么栈上的东西必须是定长的?
这是理解 Rust 所有权系统的前提。 主讲人从 RISC-V 汇编的角度给出了最底层的解释。
struct A { int field1; int field2; };struct A a;int b = 3;int c = 5;编译为 RISC-V 汇编后:
sw a0, -28(s0) # a.field1 → 固定偏移 -28sw a1, -24(s0) # a.field2 → 固定偏移 -24sw zero, -20(s0) # b = 3 → 固定偏移 -20addi a2, zero, 5 # c = 5 → 固定偏移 -16“每一个局部变量,在微观上看起来都是一堆固定的偏移。基于栈顶指针 SP 这个基准去访问你所有数据结构的成员。这就是栈必须要定死的原因——它要能用一个固定的偏移去访问。”
如果栈上出现了变长数组,整个偏移量体系就崩溃了:要访问下面的变量,你首先得知道上面那个变长数组的实际长度——这个值只有运行时才能确定。每次变量访问都要做额外的算术运算,在函数调用的热路径上完全不可接受。
C 语言提供了 alloca() 在栈上分配动态内存,但这是一个危险的设施——分配的内存随函数返回自动释放,但如果大小不可控导致栈溢出,就是灾难。Rust 选择完全不在安全代码中暴露这种能力。
6.2 String vs &str:胖指针的两种形态
这是 Rust 中最直观展示”栈和对分离”的例子。
&str:轻量级引用(16 字节)
栈上(16 字节):┌──────────────────┬──────────────────┐│ ptr (8B) │ len (8B) ││ 指向数据的地址 │ 数据长度 │└──────┬───────────┴──────────────────┘ │ ▼堆/全局区:┌──────────────────────┐│ h e l l o │ ← 只读,不可增删└──────────────────────┘String:拥有所有权的字符串(24 字节)
栈上(24 字节):┌──────────┬──────────┬──────────┐│ ptr (8B) │ len (8B) │ cap (8B) ││ 堆数据指针│ 当前长度 │ 已分配容量 │└────┬─────┴──────────┴──────────┘ │ ▼堆:┌─────────────────────────────┐│ h e l l o │ (空闲...) │ ← 可增长(len ≤ cap)└─────────────────────────────┘String 比 &str 多了一个 cap(capacity)字段。这意味着当你想往 String 里追加字符时,如果 len < cap,直接写入即可;如果 len == cap,则自动重新分配一块更大的堆内存。
“在 C 里,str 的结尾用
\0来表达结束——这是一个非常简单朴素的方法。但在 Rust 里不这么干,要显式地把长度存下来。C 不存长度的原因是存了就有最大长度限制,而且指针变厚了。但 Rust 付出额外 8 字节的代价,换来了 O(1) 的长度查询和二进制安全。”
切片操作是 &str 最强大的用法。同一个堆数据可以被多个 &str 从不同位置、不同长度引用——它们只是”视角”不同,数据本身没有任何拷贝:
String "Hello, Rust!" 在堆上: H e l l o , R u s t ! 0 1 2 3 4 5 6 7 8 9 10 11
&str[0..5] → "Hello" (ptr→0, len=5) ← 零拷贝切片! &str[7..11] → "Rust" (ptr→7, len=4) &str[0..12] → 整个串 (ptr→0, len=12)⚠️ 注意:
str是 UTF-8 变长编码,切片时如果切到了某个多字节字符的中间位置,程序会 panic。“切到一个字符的一半是没意义的。” Rust 在运行时会检查切片边界是否落在有效的 UTF-8 字符边界上——这也是它”严谨性”的体现。
七、控制流与函数:一切皆表达式
视频后半段的核心主题可以浓缩为一句话:在 Rust 中,几乎一切都是表达式。
7.1 表达式 vs 语句
但在进入控制流之前,有一个更基础的语法习惯需要先适应:
7.0 名字在前,类型在后
let x: i32 = 10; // Rust: 名字 x,类型 i32 在后面// vsint x = 10; // C: 类型 int 在前面,名字 x 在后面“你表面上看它只是个约定习俗的问题。但随着你体会深了以后,你会感觉到名字在前面更合理。”
C 语言的类型声明经常出现”名字被夹在中间”的混乱情况:
int *x; // 类型在前,名字在后 — OKint x[3]; // 名字在中间,数组标记在后面 — 开始乱了int (*f)(int); // 名字被包裹在中间 — 完全乱了“表达数组时
[3]又在后面,表达指针时*又在前面——乱套。在 Rust 里面不存在这种歧义性,它全在后面。”
名字、类型、值这三个东西哪个最重要?名字最重要——因为后续所有操作都靠名字来引用。类型定一次就够了,值是运行时才知道的。
“名字是给人读的。名字优先放前面,越琢磨越合理。”
现代语言在这点上已经达成一致:Go、Swift、OCaml、Haskell 都是名字在前。Rust 的抽象层(trait 系统)直接借鉴自 OCaml。C 的这种”类型在前”是早期设计选择,C++ 为了兼容 C 只能延续。
7.1 表达式 vs 语句
| 表达式(Expression) | 语句(Statement) | |
|---|---|---|
| 有返回值? | ✅ 有 | ❌ 没有 |
| 能传参? | ✅ 能作为函数参数 | ❌ 不能 |
| 能赋值? | ✅ 能放等号右边 | ❌ 不能 |
| 例子 | 5 + 3、if true { 1 } else { 2 } | let x = 1;、return; |
| 结尾 | 不加分号 | 加分号 |
“分号结尾是在结束一个表达式。一个东西加分号结束,它就不是表达式了——它不能传参,不能赋值。不加分号,它就是一个表达式,可以用在任何需要值的地方。”
验证一个东西是不是表达式的简单方法:看看它能不能作为参数传给函数。
7.2 if 是表达式
let x = if n < 10 { n } else { 10 };C 的 if 是纯控制流,不能返回值。Rust 把 if 变成了表达式。Python 也有类似写法(a = 30 if b < 20 else 50),但 Python 的 if 表达式和 if 控制流是两套语法。Rust 统一了——你写的 if 既是控制流也是表达式。
7.3 loop 是表达式
let result = loop { if done { break 42; } // break 带值};“他在原来 C 语言的基础上扩展了这个能力——循环里可以 break 出来一个返回值。”
break 还可以带标签一次性跳出多层嵌套循环。
7.4 裸大括号也是表达式
let x = { let a = compute(); a + 1 // 最后一行不加分号 → 整个块的返回值};大括号创建新作用域,C 里做不到返回值。“你可以在里面做很多准备工作,最后一下不加分号返回出去。很自然。“出圈就释放——下节课讲所有权时会变得非常重要。
7.5 函数也是表达式
fn add_one(x: i32) -> i32 { x + 1 // 不加分号 = 返回值 // 等价于: return x + 1;}“写时间长了就会发现不加 return 更舒服。return 一般用在提早返回。”
最后一行加分号 → 编译器自动插入空元组 () 作为返回值(相当于 C 的 void)。
7.6 for 循环:语法糖
for x in 1..5 { println!("{x}"); }// 1..5 展开后 = Range { start: 1, end: 5 }“从写作层面写简单的
1..5,从理解层面按复杂的理解——去完糖以后就好理解了。Rust 里所有运算符都可以去糖。“
7.7 没有重载,没有默认参数——故意的
“C++ 既支持 overloading,又支持隐式类型转换,又支持默认参数——三个混在一起就会形成无比难受的歧义性。你传一个 U8 进去,到底走哪个版本?走第一个也成立,走第二个也成立,走第三个也成立。没有人知道 C++ 到底是怎么工作的。”
Rust 的选择:函数名不能重复、没有隐式转换、没有默认参数。需要重载 → 后面用 trait 做,一种完全不同的、无歧义的机制。
7.8 变量遮蔽(Shadowing)
let a = true; // a: boollet a = "hello"; // a: &str — 全新的 alet a = 42_i32; // a: i32 — 又一个全新的 a“根本就不是同一个变量,只是恰好三个名字重名——就像两个中国人名字相同,两个不同的中国人。“出圈后外层变量重新可见。不限制类型——可以拿完全不同类型的变量去遮蔽。
八、用 Rust 训练思维
“很多人说,你就算学了 Rust 以后不去用 Rust,你也会对其他的语言有一个新的理解。”
Rust 把所有在其他语言中隐性存在的概念——所有权、生命周期、借用规则——全部提到了明面上。
“在其他语言里面这些都是潜规则。在 Rust 中全是明面上的规则。你训练好这个思维方式以后,你再去写 C 和 C++ 的时候,你就很不自然——‘哎,这个内存他怎么没考虑到?那个怎么没考虑到?‘“
引用索引
书籍/文档:
- 《Comprehensive Rust》(Google,4 天课程)— 本课使用的教材 - 链接
- 《The Rust Programming Language》(官方 Rust Book)— “有点长,有中文版” - 链接
- Rust Course — “比较适合国人口味,简洁不废话” - 链接
- 《Coders at Work》(Peter Seibel,2009)— “香蕉与大猩猩”比喻的原始出处 - Joe Armstrong 访谈
工具/仓库:
- crates.io — Rust 包仓库,约 15 万个包 - 链接
- Slint(23k+ stars)— 声明式 GUI,支持嵌入式 - 链接
- SQLx(17k+ stars)— 编译期 SQL 检查 - 链接
- cheats.rs — Rust 语法速查表 - 链接
- embedded-hal — 嵌入式硬件抽象层 trait 标准 - 链接
- Embassy(9.6k stars)— 嵌入式异步运行时 - 链接
概念出处:
- “香蕉与大猩猩” — Joe Armstrong,《Coders at Work》(2009)
- “为什么只有 C++ 开发者喜欢实现自己的 string?” — 知乎讨论,视频中引用解释 C 生态碎片化
- UTF-8 变长编码 — 类似 Huffman 编码原理:常见字符用短编码,生僻字符用长编码
原视频
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时