Rust + 嵌入式:強(qiáng)力開(kāi)發(fā)組合
Rust 的由來(lái)?
Rust 編程語(yǔ)言的靈感誕生于一次意外。2006年,當(dāng) Graydon Hoare 回到位于溫哥華的公寓時(shí),發(fā)現(xiàn)電梯又因?yàn)檐浖罎⒊隽斯收?。住?21 樓的他無(wú)奈爬樓時(shí),不禁心想,“我們搞計(jì)算機(jī)的,怎么連個(gè)能正常運(yùn)行的電梯都做不出來(lái)!” 這次經(jīng)歷后,Hoare 開(kāi)始著手設(shè)計(jì)一門新的編程語(yǔ)言,他希望這門語(yǔ)言可以在不引入內(nèi)存錯(cuò)誤的同時(shí),產(chǎn)出更短、更快的代碼。?

時(shí)光流轉(zhuǎn),已經(jīng)過(guò)去了 18 年,Rust 現(xiàn)已成為全球炙手可熱的新興編程語(yǔ)言,每年都吸引著越來(lái)越多的關(guān)注。2020 年第一季度時(shí),約有 60 萬(wàn)開(kāi)發(fā)者使用 Rust 進(jìn)行開(kāi)發(fā),到了 2022 年第一季度,使用人數(shù)已增長(zhǎng)至 220 萬(wàn)。著名的科技巨頭,如 Mozilla、Dropbox、Cloudflare、Discord、Facebook(Meta)、Microsoft 等,都在代碼庫(kù)中廣泛運(yùn)用 Rust 語(yǔ)言。?

根據(jù) 2021 年開(kāi)發(fā)者調(diào)查統(tǒng)計(jì),Rust 語(yǔ)言已連續(xù)六年位列“開(kāi)發(fā)者最愛(ài)的編程語(yǔ)言”榜首。 ?
應(yīng)用于嵌入式開(kāi)發(fā)的編程語(yǔ)言?
比起 Web 開(kāi)發(fā)和桌面開(kāi)發(fā),嵌入式開(kāi)發(fā)受到的關(guān)注較為有限,背后的原因或許涉及以下幾個(gè)方面:?
硬件限制:嵌入式系統(tǒng)有限的硬件資源(如性能和內(nèi)存)增大了軟件開(kāi)發(fā)的難度。?
市場(chǎng)限制:相對(duì)于 Web 和桌面應(yīng)用,嵌入式市場(chǎng)較小,嵌入式編程開(kāi)發(fā)者所獲得的經(jīng)濟(jì)回報(bào)相對(duì)較低。?
底層專業(yè)知識(shí)要求:嵌入式開(kāi)發(fā)要求開(kāi)發(fā)者具備對(duì)具體硬件和底層編程語(yǔ)言的專業(yè)知識(shí)。?
開(kāi)發(fā)周期較長(zhǎng):相對(duì)于為 Web 和桌面應(yīng)用開(kāi)發(fā)軟件,為嵌入式系統(tǒng)開(kāi)發(fā)軟件需要對(duì)代碼進(jìn)行測(cè)試和優(yōu)化,以滿足特定硬件要求,耗費(fèi)時(shí)間更長(zhǎng)。?
底層編程語(yǔ)言限制:匯編語(yǔ)言和 C 語(yǔ)言等底層編程語(yǔ)言為開(kāi)發(fā)者提供的抽象較少,而直接訪問(wèn)硬件資源和內(nèi)存可能會(huì)引入內(nèi)存錯(cuò)誤。?
上述的幾個(gè)方面足以讓我們感受到嵌入式開(kāi)發(fā)的獨(dú)特之處,也解釋了為什么對(duì)于年輕程序員來(lái)說(shuō),嵌入式開(kāi)發(fā)并不像 Web 開(kāi)發(fā)那么受歡迎。使用 Python、JavaScript 或 C# 等常見(jiàn)的現(xiàn)代編程語(yǔ)言進(jìn)行開(kāi)發(fā)時(shí),開(kāi)發(fā)者并不需要計(jì)算每個(gè)處理器周期或內(nèi)存中使用的每個(gè)千字節(jié),這樣雖然便利但極大的變化,會(huì)導(dǎo)致開(kāi)發(fā)者在初次接觸嵌入式開(kāi)發(fā)時(shí)很難適應(yīng),不論是初學(xué)者還是經(jīng)驗(yàn)豐富的 Web/桌面/移動(dòng)開(kāi)發(fā)者,都極具挑戰(zhàn)性。因此,有必要設(shè)計(jì)一門專用于嵌入式開(kāi)發(fā)的現(xiàn)代編程語(yǔ)言。?
為什么選擇 Rust??
Rust 是一門發(fā)展時(shí)間較短的現(xiàn)代編程語(yǔ)言,它專注于內(nèi)存和線程安全,旨在開(kāi)發(fā)可靠且安全的軟件。此外,Rust 對(duì)并發(fā)和并行的支持可以實(shí)現(xiàn)對(duì)資源的高效利用,這一點(diǎn)在嵌入式開(kāi)發(fā)中尤為重要。Rust 的應(yīng)用逐漸廣泛,其生態(tài)系統(tǒng)也逐漸成型,越來(lái)越多追求高效與安全性的開(kāi)發(fā)者都開(kāi)始將其作為理想的開(kāi)發(fā)語(yǔ)言。這些都是在嵌入式開(kāi)發(fā)或是注重安全性和可靠性的項(xiàng)目中選擇 Rust 的主要原因。?
Rust 的優(yōu)勢(shì)(與 C 和 C++ 相比)?
內(nèi)存安全: Rust 通過(guò)使用所有權(quán)和借用的概念,有效地消除了空指針解引用和緩沖區(qū)溢出等內(nèi)存相關(guān)的錯(cuò)誤。也就是說(shuō),通過(guò)所有權(quán)和借用系統(tǒng),Rust 可以保證編譯時(shí)的內(nèi)存安全。因?yàn)樵谇度胧介_(kāi)發(fā)中,由于內(nèi)存和資源的限制,檢測(cè)和解決內(nèi)存相關(guān)錯(cuò)誤存在困難,所以Rust 提供的內(nèi)存安全保證尤為重要。?
并發(fā)安全:Rust 完美支持零成本抽象、并發(fā)編程和多線程,通過(guò)內(nèi)置 async/await 語(yǔ)法和強(qiáng)大的類型系統(tǒng),有效地消除數(shù)據(jù)競(jìng)爭(zhēng)等常見(jiàn)的并發(fā)錯(cuò)誤。該特性不僅適用于嵌入式系統(tǒng),各個(gè)領(lǐng)域的開(kāi)發(fā)者都可以借此輕松寫出安全高效的并發(fā)代碼。?
高性能:Rust 的性能與 C 和 C++ 旗鼓相當(dāng),Rust 還能提供更可靠的內(nèi)存安全和并發(fā)安全。?
代碼可讀性:相較于 C 和 C++, Rust 的語(yǔ)法設(shè)計(jì)更注重代碼可讀性和降低錯(cuò)誤率,它引入了模式匹配、類型推導(dǎo)和函數(shù)式編程結(jié)構(gòu)等特性,使代碼的編寫與維護(hù)更為便捷,尤其適用于較為復(fù)雜的大型項(xiàng)目。?
日益壯大的生態(tài)系統(tǒng):Rust 的生態(tài)系統(tǒng)包括各種庫(kù)?(crates)?、工具和資源,可供包括嵌入式開(kāi)發(fā)在內(nèi)的各開(kāi)發(fā)領(lǐng)域使用。隨著生態(tài)系統(tǒng)的不斷發(fā)展壯大,開(kāi)發(fā)者可以輕松使用 Rust 搭建工程,并找到所需的特定支持和資源。?
包管理器和構(gòu)建系統(tǒng):Rust 內(nèi)置 Cargo 包管理器,用于自動(dòng)化構(gòu)建、測(cè)試和發(fā)布流程,同時(shí)也能創(chuàng)建新項(xiàng)目并管理依賴項(xiàng)。?
Rust 的劣勢(shì)(與 C 和 C++ 相比)?
雖然優(yōu)勢(shì)明顯,但 Rust 并非一門完美的語(yǔ)言,與其他編程語(yǔ)言(包括但不限于 C和 C++)相比,它也存在一些不足之處。?
學(xué)習(xí)曲線陡峭:Rust 的學(xué)習(xí)曲線比起 C 等多數(shù)編程語(yǔ)言更為陡峭。開(kāi)發(fā)者們需要花費(fèi)更多的時(shí)間和精力來(lái)理解它的獨(dú)特特性,如上文所提到的所有權(quán)、借用系統(tǒng)等。這對(duì)初次接觸 Rust 的開(kāi)發(fā)者來(lái)說(shuō),可能會(huì)是一種挑戰(zhàn)。?
編譯時(shí)間長(zhǎng):和其他語(yǔ)言比起來(lái),Rust 擁有高級(jí)類型系統(tǒng)和借用檢查器,需要花費(fèi)更長(zhǎng)的編譯時(shí)間,這一點(diǎn)在大型工程中尤為明顯。?
工具支持不足:盡管 Rust 的生態(tài)系統(tǒng)正在迅速擴(kuò)展,但 C 和 C++ 等更加成熟的編程語(yǔ)言已經(jīng)存在了幾十年,積累了大量的代碼庫(kù)。相較之下,Rust 的工具支持還是略顯不足,在特定項(xiàng)目中找到并使用合適工具的難度相較略高。?
底層控制不足:相較于 C 和 C++,Rust 的安全特性可能會(huì)限制其底層控制能力。盡管 Rust 仍支持特定底層優(yōu)化或是直接與硬件交互,但難度略高。?
社區(qū)支持不足:與 C 和 C++ 等更為成熟的編程語(yǔ)言相比,Rust 仍然是一門相對(duì)較新的編程語(yǔ)言。它的社區(qū)規(guī)模較小,貢獻(xiàn)代碼的開(kāi)發(fā)者和可供使用的資源、庫(kù)和工具也相對(duì)較少。?
綜上所述,與 C ?和 C++ 等傳統(tǒng)的嵌入式開(kāi)發(fā)語(yǔ)言相比,Rust 在內(nèi)存安全、并發(fā)支持、性能、代碼可讀性以及生態(tài)系統(tǒng)等方面都具有諸多優(yōu)勢(shì)。因此,在嵌入式開(kāi)發(fā)中,尤其是在注重安全、可靠性和穩(wěn)定性的項(xiàng)目中,使用 Rust 的開(kāi)發(fā)者數(shù)量穩(wěn)步上升。但相較于 C 和 C++,Rust 的不足之處往往與其作為一門相對(duì)較新的語(yǔ)言以及獨(dú)特的特性有關(guān)。不過(guò),Rust 的優(yōu)勢(shì)已足以使其成為某些項(xiàng)目的不二之選。?
如何運(yùn)行 Rust 代碼??
根據(jù)不同的環(huán)境和應(yīng)用需求,可以通過(guò)多種方式運(yùn)行基于 Rust 的固件,此類固件通常支持在宿主環(huán)境或裸機(jī)環(huán)境下運(yùn)行。接下來(lái)將詳細(xì)介紹這兩種環(huán)境。?
什么是宿主環(huán)境??
與普通的 PC 環(huán)境類似,Rust 的宿主環(huán)境提供了一個(gè)可供構(gòu)建 Rust 標(biāo)準(zhǔn)庫(kù) (std) 的操作系統(tǒng)。標(biāo)準(zhǔn)庫(kù)(std)是包含在各個(gè) Rust 安裝程序中的模塊和類型的集合,支持多種用于構(gòu)建 Rust 程序的功能,包括數(shù)據(jù)結(jié)構(gòu)、配網(wǎng)、互斥鎖和其他同步原語(yǔ)、輸入/輸出等。?
在宿主環(huán)境 (簡(jiǎn)稱為 std) 下,開(kāi)發(fā)者可以使用基于 C 的 ESP-IDF 開(kāi)發(fā)框架中的功能。ESP-IDF 提供的 newlib 環(huán)境支持開(kāi)發(fā)者在該框架之上構(gòu)建 Rust 標(biāo)準(zhǔn)庫(kù)。還可以將 ESP-IDF 作為操作系統(tǒng)來(lái)構(gòu)建 Rust 應(yīng)用程序。因此除上述列出的所有標(biāo)準(zhǔn)庫(kù)功能外,還可以使用 ESP-IDF API 中實(shí)現(xiàn)基于 C 的功能。?
以下為在 ESP-IDF (FreeRTOS) 上運(yùn)行的 blinky 示例(更多示例存放在 esp-idf-hal 倉(cāng)庫(kù)中):?
適用宿主環(huán)境的情況?
實(shí)現(xiàn)多項(xiàng)功能:如需在嵌入式系統(tǒng)實(shí)現(xiàn)大量功能,比如支持網(wǎng)絡(luò)協(xié)議、文件輸入/輸出或者引入復(fù)雜的數(shù)據(jù)結(jié)構(gòu),可以考慮使用宿主環(huán)境,該環(huán)境下的標(biāo)準(zhǔn)庫(kù)提供豐富的功能,幫助您快速高效地構(gòu)建復(fù)雜的應(yīng)用程序。?
要求可移植性:標(biāo)準(zhǔn)庫(kù)提供了一套支持在不同平臺(tái)和架構(gòu)上使用的標(biāo)準(zhǔn)化 API,方便編寫具有可移植性和重用性的代碼。?
進(jìn)行快速開(kāi)發(fā):標(biāo)準(zhǔn)庫(kù)提供了豐富的功能集,可以快速高效地構(gòu)建應(yīng)用程序,無(wú)需擔(dān)心底層細(xì)節(jié)。?
什么是裸機(jī)環(huán)境? ?
裸機(jī)環(huán)境是指沒(méi)有可供使用的操作系統(tǒng)環(huán)境。當(dāng)編譯的 Rust 程序擁有 no_std 屬性時(shí),該程序無(wú)權(quán)訪問(wèn)上述 std 章節(jié)中提到的某些特定功能。盡管仍支持使用配網(wǎng)或引入復(fù)雜數(shù)據(jù)結(jié)構(gòu)等功能,但實(shí)現(xiàn)方式將會(huì)更加復(fù)雜。 no_std 程序依賴于 Rust 所有環(huán)境中可用的核心語(yǔ)言特性,包括數(shù)據(jù)類型、控制結(jié)構(gòu)和底層內(nèi)存管理。此環(huán)境在嵌入式編程中非常實(shí)用,特別適用于內(nèi)存資源有限、需要對(duì)硬件進(jìn)行低級(jí)別控制的場(chǎng)景。?
以下為在裸機(jī)環(huán)境上(不借助操作系統(tǒng))運(yùn)行的 blinky 示例(更多示例存放在 esp-hal 倉(cāng)庫(kù)中):?
適用裸機(jī)環(huán)境的情況?
減少內(nèi)存占用:如果嵌入式系統(tǒng)資源有限,需要占用較小的內(nèi)存,可以考慮使用裸機(jī)環(huán)境,因?yàn)?std 會(huì)顯著增加最終二進(jìn)制文件的大小和編譯時(shí)間。?
實(shí)現(xiàn)直接硬件控制:如果需要在嵌入式系統(tǒng)中實(shí)現(xiàn)直接硬件控制,例如實(shí)現(xiàn)底層設(shè)備驅(qū)動(dòng)程序或訪問(wèn)特定硬件功能,可以考慮使用裸機(jī)環(huán)境,因?yàn)?std 的抽象層會(huì)提高直接與硬件進(jìn)行交互的難度。?
涉及實(shí)時(shí)約束或?qū)r(shí)間敏感的應(yīng)用程序:如果嵌入式系統(tǒng)要求實(shí)時(shí)性能或低延遲響應(yīng)時(shí)間,可以考慮使用裸機(jī)環(huán)境,因?yàn)?std 可能導(dǎo)致意外延遲和開(kāi)銷。?
自定義需求:裸機(jī)環(huán)境支持更多自定義配置,同時(shí)也能實(shí)現(xiàn)對(duì)應(yīng)用程序行為的精細(xì)控制,非常適用于特定或非標(biāo)準(zhǔn)環(huán)境。?
小結(jié):要不要從 C 轉(zhuǎn)到 Rust 呢??
如果您打算新開(kāi)發(fā)一個(gè)對(duì)內(nèi)存安全或是并發(fā)性有要求的工程或任務(wù),可以考慮從 C 轉(zhuǎn)到 Rust。但對(duì)于已經(jīng)用 C 開(kāi)發(fā)完畢且正常運(yùn)行的工程,就沒(méi)有為 Rust 而重新編寫并測(cè)試整個(gè)代碼庫(kù)的必要了。使用 Rust 代碼調(diào)用 C 函數(shù)的難度并不大,因此對(duì)于這種工程,可以考慮保留已有的 C 代碼,并使用 Rust 編寫新添加的功能以及模塊。此外,也可以嘗試使用 Rust 編寫 ESP-IDF 組件。總而言之,是否要從 C 轉(zhuǎn)向 Rust 需根據(jù)具體的需求進(jìn)行權(quán)衡。?

參考鏈接?
How Rust went from a side project to the world’s most-loved programming language | MIT Technology Review?
https://www.technologyreview.com/2023/02/14/1067869/rust-worlds-fastest-growing-programming-language/
Announcing Rust 1.0 | Rust Blog (rust-lang.org)?
https://blog.rust-lang.org/2015/05/15/Rust-1.0.html
4 years of Rust | Rust Blog (rust-lang.org)?
https://blog.rust-lang.org/2019/05/15/4-Years-Of-Rust.html
The state of the Rust market in 2023 (yalantis.com)?
https://yalantis.com/blog/rust-market-overview/
Stack Overflow Developer Survey 2021?
https://insights.stackoverflow.com/survey/2021#most-loved-dreaded-and-wanted
https://docs.rust-embedded.org/book/intro/no-std.html#hosted-environments?
ESP-IDF
https://github.com/espressif/esp-idf
blinky 示例
https://github.com/esp-rs/esp-idf-hal/blob/master/examples/blinky.rs
esp-idf-hal?倉(cāng)庫(kù)
https://github.com/esp-rs/esp-idf-hal/tree/master/examples
esp-hal?倉(cāng)庫(kù)
https://github.com/esp-rs/esp-hal/tree/main
Rust 編寫 ESP-IDF 組件
https://github.com/espressif/rust-esp32-example