1. 嵌入式Rust生态全景:为什么是Rust?

各位同学,欢迎来到《Rust Embedded HAL硬件抽象层 嵌入式R驱动开发实战》的第一章。

我先问大家一个问题:你写嵌入式代码时,最怕什么?

我猜很多人会回答——内存越界、野指针、死锁。没错,这些C语言里的老问题,几乎每个嵌入式工程师都踩过坑。我自己做了十几年嵌入式开发,从8051到STM32再到RISC-V,C语言陪我走了很长的路。但说实话,每次产品送样前,我最担心的不是功能没实现,而是那些运行时才暴露的内存问题。

Rust的出现,改变了这个局面。

1.1 为什么是Rust?

简单说,Rust把很多运行时才能发现的问题,提前到了编译阶段。你写代码时,编译器就帮你检查了一遍。这不是魔法,而是Rust语言设计上的根本差异。

核心观点:Rust不是要取代C,而是给嵌入式开发多了一个安全、高效、现代化的选择。尤其适合对可靠性要求极高的场景——汽车电子、医疗设备、工业控制、航空航天。

我举个例子。几年前我做一款工业传感器,代码量大概两万行C。调试阶段一切正常,量产到第三批时,现场反馈设备偶尔死机。查了整整两周,最后定位到一个中断服务函数里的全局变量竞争。这种问题在C里很难静态发现,只能靠经验和运气。如果用Rust,所有权系统和Send/Sync trait在编译期就能拦住它。

1.2 Rust在嵌入式领域的杀手锏

Rust有三个核心特性,对嵌入式开发来说是真正的杀手锏。

1.2.1 内存安全——没有GC的自动内存管理

嵌入式系统资源有限,不能像PC那样跑垃圾回收。C语言把内存管理完全交给开发者,自由度高,但风险也高。

Rust通过所有权(Ownership)、借用(Borrowing)和生命周期(Lifetime)这套机制,在编译期就保证了内存安全。没有空指针、没有野指针、没有双重释放、没有缓冲区溢出。

我的经验:刚开始学Rust的所有权时,确实有点绕。但一旦习惯了,你会发现写代码时心里踏实很多。编译器就像个严格的代码审查员,帮你堵住大部分低级错误。

特性 C语言 Rust
内存管理 手动malloc/free 所有权系统,编译期检查
空指针 NULL无处不在 Option<T>,没有空指针
缓冲区溢出 运行时检查或不管 编译期边界检查
数据竞争 靠开发者经验 Send/Sync trait保证
运行时开销 无 零成本抽象,无运行时

1.2.2 零成本抽象——写高级代码,跑底层性能

很多人觉得高级语言抽象会带来性能损失。Rust的设计哲学是你不需要为没用的东西付费,而且你写的高级抽象,编译后和手写汇编一样高效。

举个例子,Rust的迭代器、闭包、泛型,在编译时会被单态化和内联,最终生成的机器码和手写循环几乎没有区别。这在嵌入式领域太重要了——你既可以用现代语言特性提高开发效率,又不用担心性能损失。

注意:零成本抽象不等于零抽象。Rust的标准库确实有一些开销,但在嵌入式开发中,我们通常使用#![no_std]模式,只使用核心库,完全避免标准库的运行时开销。

1.2.3 Fearless Concurrency——无畏并发

嵌入式系统里,中断、多任务、多核越来越常见。并发编程的难点在于数据竞争和死锁。

Rust的Send和Sync trait,配合所有权系统,让编译器在编译期就检查出数据竞争。如果你写了一段有并发问题的代码,编译器直接报错,根本跑不起来。

我记得有一次,我在一个双核MCU项目里用Rust写共享数据访问。代码写完后编译报错,提示某个结构体没有实现Sync。我检查了一下,发现确实有个内部可变性没处理好。改完后,整个系统跑起来非常稳定。换成C的话,这种问题可能要到现场才能暴露。

1.3 嵌入式Rust生态全景

Rust在嵌入式领域的生态已经相当成熟。我画了一张图,帮你快速了解整体架构。

嵌入式Rust生态全景图 你的嵌入式应用 设备驱动 / 外设驱动 (HAL / PAC) embedded-hal / 硬件抽象层 MCU支持包 (PAC) / 启动代码 / 链接脚本 Rust编译器 / cargo / probe-rs / defmt 生态层次 安全抽象

从这张图可以看出,嵌入式Rust生态分为五个层次:

  1. 应用层:你的业务逻辑代码
  2. 驱动层:直接操作外设的代码,基于HAL或PAC
  3. HAL抽象层:embedded-hal trait,提供跨平台接口
  4. MCU支持层:芯片厂商提供的PAC、启动代码、链接脚本
  5. 工具链:编译器、构建工具、调试工具

我们这门课的核心,就是embedded-hal这一层。它让你写的驱动代码可以跨平台复用——今天在STM32上跑,明天换到nRF52,驱动代码几乎不用改。

1.4 课程目标

学完这门课,你将能够:

  • 理解embedded-hal的设计思想和核心trait
  • 独立编写跨平台的嵌入式外设驱动
  • 掌握Rust在嵌入式开发中的最佳实践
  • 能够将现有的C驱动移植到Rust
  • 具备阅读和贡献开源Rust嵌入式项目的能力

一句话总结:这门课不是教你怎么点灯,而是教你怎么写出安全、可复用、高性能的嵌入式驱动。

1.5 前置知识要求

为了跟上课程进度,你需要具备以下基础:

领域 具体要求
Rust基础 熟悉所有权、借用、生命周期、trait、泛型、枚举、模式匹配
嵌入式基础 了解MCU架构、GPIO/UART/SPI/I2C等常见外设、中断、寄存器操作
C语言基础 能读懂C代码,了解指针、结构体、位操作(方便对比学习)
工具链 安装好Rust工具链、cargo、目标平台支持(如thumbv7em-none-eabihf)

我的建议:如果你Rust基础还不太牢,可以先花一周时间过一遍《Rust程序设计语言》(The Book)的前10章。嵌入式Rust对Rust语言本身的要求并不高,但所有权和trait是必须掌握的。

1.6 对比C语言:Rust到底好在哪?

我无意贬低C语言。C在嵌入式领域统治了几十年,有最成熟的生态、最丰富的资料、最广泛的硬件支持。但时代在变,需求在变。

我们来看一个具体的对比场景:操作一个GPIO输出引脚。

C语言实现:

// 假设GPIOA的基地址为0x40020000
#define GPIOA_ODR (*(volatile uint32_t *)0x40020014)

void gpio_set_pin(uint8_t pin) {
    GPIOA_ODR |= (1 << pin);
}

void gpio_clear_pin(uint8_t pin) {
    GPIOA_ODR &= ~(1 << pin);
}

Rust实现(使用PAC):

// 使用svd2rust生成的PAC
let gpioa = unsafe { pac::GPIOA::steal() };
gpioa.odr.modify(|r, w| unsafe { w.bits(r.bits() | (1 << pin)) });

Rust实现(使用HAL):

// 使用embedded-hal的OutputPin trait
let mut led = gpioa.pa5.into_push_pull_output();
led.set_high().unwrap();
led.set_low().unwrap();

看出区别了吗?

  • C代码直接操作寄存器地址,没有任何类型安全
  • Rust的PAC层虽然也用了unsafe,但通过类型系统限制了操作范围
  • Rust的HAL层完全安全,你不可能误操作其他寄存器

我曾经踩过的坑:用C写驱动时,有一次不小心把GPIOA的地址写成了0x40020010(其实是0x40020000),结果调试了整整一天才发现是地址写错了。Rust的HAL层通过类型系统,从根本上杜绝了这种低级错误。

1.7 本章小结

这一章我们聊了:

  • 为什么嵌入式开发需要Rust——安全、高效、现代化
  • Rust的三大杀手锏——内存安全、零成本抽象、无畏并发
  • 嵌入式Rust生态的五个层次——从应用到工具链
  • 课程目标和前置知识要求

从下一章开始,我们会正式进入embedded-hal的世界。你会看到那些trait是怎么设计的,以及如何用它们写出优雅的跨平台驱动。

准备好了吗?我们开始吧。