本教程的第二部分,我們將深入理解 Rust 語言的核心概念——所有權(Ownership)、借用(Borrowing)和生命周期(Lifetimes)。這些是 Rust 內存安全的基礎,也是初學者理解 Rust 最關鍵的部分。理解它們后,我們還將探討如何準備一塊實際的開發板,為真正點亮 LED 做準備。
4. Rust 核心概念:所有權、借用和生命周期
Rust 最大的特點就是它的內存安全模型,它在編譯時而不是運行時進行內存管理,從而避免了 C/C++ 中常見的內存錯誤,如空指針引用、數據競爭等。這主要通過三個核心概念實現:所有權、借用和生命周期。
4.1 所有權 (Ownership)
所有權是 Rust 的核心特性。 每個值在 Rust 中都有一個所有者 (owner)。
規則 1: 每個值都有且只有一個所有者。
規則 2: 當所有者超出其作用域 (scope) 時,值會被丟棄 (dropped),其內存也會被自動回收。
示例:
fn main() {let s1 = String::from("hello"); // s1 擁有 "hello" 這個字符串數據let s2 = s1; // 這里發生了“移動”(move),s1 的所有權被轉移給了 s2// println!("{}", s1); // 錯誤!s1 已經不再擁有數據了,會報錯:value borrowed here after moveprintln!("{}", s2); // 正確,s2 現在是數據的所有者
} // s2 超出作用域,"hello" 內存被釋放
在嵌入式開發中,這意味著你不再需要手動調用 free()
或 delete
,也不用擔心忘記釋放內存而導致內存泄漏。Rust 編譯器會在編譯時替你處理好這一切。
4.2 借用 (Borrowing)
如果你不想轉移所有權,但又需要使用某個值,該怎么辦?這就是借用的作用。你可以通過引用 (&
) 來借用一個值,而不是轉移它的所有權。
規則 1: 在任意給定時間,你只能擁有一個可變引用 (
&mut T
) 或任意數量的不可變引用 (&T
)。規則 2: 引用必須總是有效的。
示例:
fn main() {let mut s = String::from("hello"); // s 是可變的 String// 不可變借用:可以有多個let r1 = &s; // r1 借用了 slet r2 = &s; // r2 也借用了 sprintln!("{} and {}", r1, r2); // 正確,可以同時使用多個不可變引用// s 和 r1, r2 不再被使用后,這些不可變借用結束// 可變借用:只能有一個let r3 = &mut s; // r3 可變地借用了 sr3.push_str(", world!"); // 可以通過 r3 修改 s 的值println!("{}", r3); // 正確// println!("{}", s); // 錯誤!當 r3 還在活躍時,不能再使用 s,因為可變借用是獨占的。// 如果這里需要使用 s,r3 必須先不再被使用或超出作用域。
}
在嵌入式開發中,借用規則對于硬件寄存器訪問和**共享資源(如外設)**的管理至關重要。它能防止你在同一時間對同一個寄存器進行不安全的并發修改,或者在讀取的同時又嘗試寫入。
4.3 生命周期 (Lifetimes)
生命周期是 Rust 編譯器用來確保所有借用都是有效的機制。 它們表示引用能夠保持有效的作用域。
規則: 引用的生命周期不能超過其所引用值的生命周期。
示例:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {if x.len() > y.len() {x} else {y}
}fn main() {let string1 = String::from("abcd");let string2 = "xyz"; // 字面量 'static 生命周期// 這里,longest 函數返回的引用,其生命周期會是 string1 和 string2 中較短的那個。// 'a 標注確保了編譯器在編譯時檢查引用的有效性。let result = longest(&string1, &string2);println!("The longest string is {}", result);
}
在嵌入式開發中,生命周期通常在處理外設驅動程序或裸機內存區域時顯得尤為重要。例如,如果你有一個引用指向某個外設寄存器,生命周期會確保這個引用在寄存器被釋放或重置之前一直是有效的,從而避免訪問已經不存在的內存。
5. 準備你的實際開發板
現在你對 Rust 的核心概念有了基本理解,是時候準備一塊真實的微控制器板,讓你的 Rust 代碼在硬件上跑起來了!
5.1 選擇一塊開發板
對于初學者,我強烈推薦使用一個內置調試器的開發板,例如:
STM32 Nucleo 系列: (推薦,價格適中,資料豐富,有內置 ST-Link 調試器)
例如:STM32F401RE Nucleo-64 或 STM32F411RE Nucleo-64。它們通常基于 Cortex-M4 處理器。
STM32 Discovery 系列: 功能更強大,也通常內置調試器。
Microbit (V2): 盡管它內置調試器(DAPLink),但其 Cortex-M4F 處理器相對較小,且生態系統更偏向教育。
Raspberry Pi Pico: 基于 RP2040 (雙 Cortex-M0+),價格非常便宜,內置調試(DAPLink)支持,生態也發展迅速。
購買建議: 如果你還沒有開發板,**一塊帶有 ST-Link 或 DAPLink 調試器的 STM32 Nucleo/Discovery 板是一個非常好的起點。**它們可以直接通過 USB 連接到電腦進行燒錄和調試,無需額外購買調試器。
5.2 安裝板級工具和驅動
一旦你有了開發板,你需要安裝一些工具和驅動程序,以便你的電腦能與開發板通信。
安裝板級驅動:
ST-Link 驅動 (針對 STM32 Nucleo/Discovery): 訪問 STMicroelectronics 官網,搜索并下載安裝 ST-Link 驅動。
Windows 用戶通常需要安裝驅動。
Linux 和 macOS 通常自帶
libusb
,但可能需要安裝額外的 udev 規則來允許非 root 用戶訪問 USB 設備(具體請搜索 "ST-Link udev rules Linux")。
DAPLink / J-Link 驅動 (如果你使用 Microbit V2 或其他): DAPLink 通常是免驅動的,因為它模擬了一個 USB 大容量存儲設備。J-Link 需要安裝 SEGGER 提供的驅動。
安裝
openocd
(開放片上調試器):openocd
是一個用于調試和燒錄微控制器的開源工具。Linux (Debian/Ubuntu):
sudo apt install openocd
macOS (Homebrew):
brew install openocd
Windows: 從 OpenOCD 的 GitHub 頁面或官方下載渠道獲取預編譯的二進制文件,并將其路徑添加到系統環境變量
PATH
中。
驗證
openocd
安裝: 將你的開發板通過 USB 線連接到電腦,然后在終端中運行:openocd --version
如果顯示版本號,說明安裝成功。要測試它是否能識別你的板子,你可以嘗試運行針對你板子的配置命令(例如:
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
,具體配置取決于你的板子型號)。
5.3 燒錄和調試工具的額外配置 (可選但推薦)
在 第一部分 我們安裝了 probe-run
,它是一個方便的工具,可以結合 openocd
和 GDB
來直接燒錄和運行 Rust 嵌入式程序。
確保 .cargo/config.toml
文件中配置了 runner
。如果你使用了 cortex-m-quickstart
模板,這個文件通常已經配置好了,類似這樣:
Ini, TOML
# .cargo/config.toml
[build]
target = "thumbv7em-none-eabihf" # 或者你的目標[target.thumbv7em-none-eabihf] # 或者你的目標
runner = "probe-run --chip STM32F401RETx" # 替換為你的芯片型號# 例如:STM32F411RETx, nRF52840 etc.
如何找到你的芯片型號? 通常印在微控制器芯片本體上,或者查看開發板的說明書。例如,STM32F401RE Nucleo-64 板上是 STM32F401RET6。你只需要提供前綴,例如 STM32F401RETx
。
下一步
現在,你已經理解了 Rust 的核心內存安全概念,并且準備好了你的實際開發板和必要的工具。
在 第三部分,我們將編寫一個真正的嵌入式 "Hello, LED!" 程序,并將其燒錄到你的開發板上,親眼看到 Rust 代碼在硬件上運行的效果!