Rust 的由来
Rust 编程语言的灵感诞生于一次意外。2006年,当 Graydon Hoare 回到位于温哥华的公寓时,发现电梯又因为软件崩溃出了故障。住在 21 楼的他无奈爬楼时,不禁心想,“我们搞计算机的,怎么连个能正常运行的电梯都做不出来!” 这次经历后,Hoare 开始着手设计一门新的编程语言,他希望这门语言可以在不引入内存错误的同时,产出更短、更快的代码 [1] 。
您可以前往 [2] 和 [3],进一步了解 Rust 的发展。
时光流转,已经过去了 18 年,Rust 现已成为全球炙手可热的新兴编程语言,每年都吸引着越来越多的关注。2020 年第一季度时,约有 60 万开发者使用 Rust 进行开发,到了 2022 年第一季度,使用人数已增长至 220 万 [4]。著名的科技巨头,如 Mozilla、Dropbox、Cloudflare、Discord、Facebook(Meta)、Microsoft 等,都在代码库中广泛运用 Rust 语言。
根据 2021 年开发者调查统计,Rust 语言已连续六年位列“开发者最爱的编程语言”榜首。
比起 Web 开发和桌面开发,嵌入式开发受到的关注较为有限,背后的原因或许涉及以下几个方面:
上述的几个方面足以让我们感受到嵌入式开发的独特之处,也解释了为什么对于年轻程序员来说,嵌入式开发并不像 Web 开发那么受欢迎。使用 Python、JavaScript 或 C# 等常见的现代编程语言进行开发时,开发者并不需要计算每个处理器周期或内存中使用的每个千字节,这样虽然便利但极大的变化,会导致开发者在初次接触嵌入式开发时很难适应,不论是初学者还是经验丰富的 Web/桌面/移动开发者,都极具挑战性。因此,有必要设计一门专用于嵌入式开发的现代编程语言。
Rust 是一门发展时间较短的现代编程语言,它专注于内存和线程安全,旨在开发可靠且安全的软件。此外,Rust 对并发和并行的支持可以实现对资源的高效利用,这一点在嵌入式开发中尤为重要。Rust 的应用逐渐广泛,其生态系统也逐渐成型,越来越多追求高效与安全性的开发者都开始将其作为理想的开发语言。这些都是在嵌入式开发或是注重安全性和可靠性的项目中选择 Rust 的主要原因。
虽然优势明显,但 Rust 并非一门完美的语言,与其他编程语言(包括但不限于 C 和 C++)相比,它也存在一些不足之处。
综上所述,与 C 和 C++ 等传统的嵌入式开发语言相比,Rust 在内存安全、并发支持、性能、代码可读性以及生态系统等方面都具有诸多优势。因此,在嵌入式开发中,尤其是在注重安全、可靠性和稳定性的项目中,使用 Rust 的开发者数量稳步上升。但相较于 C 和 C++,Rust 的不足之处往往与其作为一门相对较新的语言以及独特的特性有关。不过,Rust 的优势已足以使其成为某些项目的不二之选。
根据不同的环境和应用需求,可以通过多种方式运行基于 Rust 的固件,此类固件通常支持在宿主环境或裸机环境下运行。接下来将详细介绍这两种环境。
与普通的 PC 环境类似,Rust 的宿主环境提供了一个可供构建 Rust 标准库 (std) 的操作系统。标准库(std)是包含在各个 Rust 安装程序中的模块和类型的集合,支持多种用于构建 Rust 程序的功能,包括数据结构、配网、互斥锁和其他同步原语、输入/输出等。
在宿主环境 (简称为 std) 下,开发者可以使用基于 C 的 ESP-IDF 开发框架中的功能。ESP-IDF 提供的 newlib 环境支持开发者在该框架之上构建 Rust 标准库。还可以将 ESP-IDF 作为操作系统来构建 Rust 应用程序。因此除上述列出的所有标准库功能外,还可以使用 ESP-IDF API 中实现基于 C 的功能。
以下为在 ESP-IDF (FreeRTOS) 上运行的 blinky 示例(更多示例存放在 esp-idf-hal 仓库中):
// 导入示例所需外设
use esp_idf_hal::delay::FreeRtos;
use esp_idf_hal::gpio::*;
use esp_idf_hal::peripherals::Peripherals;
// Start of our main function i.e entry point of our example
fn main() -> anyhow::Result<()> {
// 应用所需 ESP-IDF 补丁
esp_idf_sys::link_patches();
// 初始化所有所需外设
let peripherals = Peripherals::take().unwrap();
// 创建一个 led 对象,将其设置为 GPIO4 引脚的输出模式
let mut led = PinDriver::output(peripherals.pins.gpio4)?;
// 设置一个每 500 毫秒即切换 LED 开/关状态的无限循环
loop {
led.set_high()?;
// 进入睡眠模式,以防触发看门狗
FreeRtos::delay_ms(1000);
led.set_low()?;
FreeRtos::delay_ms(1000);
}
}
裸机环境是指没有可供使用的操作系统环境。当编译的 Rust 程序拥有 no_std 属性时,该程序无权访问上述 std 章节中提到的某些特定功能。尽管仍支持使用配网或引入复杂数据结构等功能,但实现方式将会更加复杂。 no_std 程序依赖于 Rust 所有环境中可用的核心语言特性,包括数据类型、控制结构和底层内存管理。此环境在嵌入式编程中非常实用,特别适用于内存资源有限、需要对硬件进行低级别控制的场景。
以下为在裸机环境上(不借助操作系统)运行的 blinky 示例(更多示例存放在 esp-hal 仓库中):
#![no_std]
#![no_main]
// 导入示例所需外设
use esp32c3_hal::{
clock::ClockControl,
gpio::IO,
peripherals::Peripherals,
prelude::*,
timer::TimerGroup,
Delay,
Rtc,
};
use esp_backtrace as _;
// 设置程序执行的起始点
// 因为这是一个 `no_std` 程序,不存在主函数
#[entry]
fn main() -> ! {
// 初始化所有所需外设
let peripherals = Peripherals::take();
let mut system = peripherals.SYSTEM.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
// 禁用看门狗定时器。对于 ESP32-C3 来说,包括 Super WDT、
// RTC WDT 和 TIMG WDT
let mut rtc = Rtc::new(peripherals.RTC_CNTL);
let timer_group0 = TimerGroup::new(
peripherals.TIMG0,
&clocks,
&mut system.peripheral_clock_control,
);
let mut wdt0 = timer_group0.wdt;
let timer_group1 = TimerGroup::new(
peripherals.TIMG1,
&clocks,
&mut system.peripheral_clock_control,
);
let mut wdt1 = timer_group1.wdt;
rtc.swd.disable();
rtc.rwdt.disable();
wdt0.disable();
wdt1.disable();
// 将 GPIO4 设置为输出,并将其初始状态设置为高电平
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
// 创建一个 led 对象,将其设置为 GPIO4 引脚的输出模式
let mut led = io.pins.gpio5.into_push_pull_output();
// 启动 LED
led.set_high().unwrap();
// 初始化延迟外设,并在循环中
// 使用它来切换 LED 的状态
let mut delay = Delay::new(&clocks);
// 设置一个每 500 毫秒即切换 LED 开/关状态的无限循环
loop {
led.toggle().unwrap();
delay.delay_ms(500u32);
}
}
如果您打算新开发一个对内存安全或是并发性有要求的工程或任务,可以考虑从 C 转到 Rust。但对于已经用 C 开发完毕且正常运行的工程,就没有为 Rust 而重新编写并测试整个代码库的必要了。使用 Rust 代码调用 C 函数的难度并不大,因此对于这种工程,可以考虑保留已有的 C 代码,并使用 Rust 编写新添加的功能以及模块。此外,也可以尝试使用 Rust 编写 ESP-IDF 组件。总而言之,是否要从 C 转向 Rust 需根据具体的需求进行权衡。