壽元是Rust因對堆棧的抽象及保證內存安全而生的(所有權外的)另一概念。

基本而言,壽元代表的是值的有效範圍。許多語言(如C++)中的作用域即是壽元的一個方面,但Rust中的壽元是一更通用/抽象且明確要求的概念。

我見到不少中文Rust相關材料中將lifetime翻譯爲「生命週期」,實在是令人瞠目結舌:「生命週期」是對lifecycle的翻譯,而這些人似乎完全沒有看到Rust中這個概念是lifetime

當然,完全存在另一種可能:他們看到了,仍然將其翻譯爲「生命週期」。這樣則是體現了當代中文翻譯中的一大問題:墨守成規和懶惰——固守已有的「詞」,寧可用錯也不願意另外造詞。當然這也是環境造就的,畢竟中文本以字爲基礎,但一則現在的教育讓人們習慣以詞(字組)爲基礎,二則現在通行的漢字數字化方案對新造漢字很不友好

「壽元」未必是最優的翻譯,但卻是我現下能想到的最信達雅的翻譯。如果有達成共識的不更差的翻譯,我個人很願意遷移。

Rust中,對壽元的需求存在於引用(借用)之上——只有引用的值纔會有值的壽元不同於變量壽元的情況 …

在今年Increase Rust's Reach中,我參與Rust新網站的i18n及l10n。其中新網站要基於 Rocket 構建,所以也就(跟着 官方教程 )學習了一下Rocket。 既然學了,就順便記錄一點心得和體會,以方便後來者。

Rocket是一個 web框架 。我個人對web編程(尤前端)並不太感興趣(主要是感到 web技術棧 太過麻煩/複雜),所以涉及不太多,之前也只用過Python那邊的Flask以及(一小段時間)Django以及Go自帶的http服務器,故而本文不怎麼會涉及和其他web框架的對比。

本文不打算成爲通常意義上的Rocket教程,而只是打算給有興趣者一個快速的(對rocket的)觀感。其中也會有一些個人的經驗教訓等。

Rocket概覽

類似我之前用過的框架,Rocket也將函數作爲不同的路由的處理器。Rocket在每個函數之前使用形如 #[get("/myroute")]屬性 作爲標記,之後在Rocket入口對象/結構體上對所需要的路由(函數)進行 mount 即可。

#[get("/")]
fn …

Rust的一大特色(甚至在官方教程中 被稱爲the most unique feature )就是其借用及所有權機制。這兩個機制由編譯器進行強制,並且可以極大限度地保證變量安全(並行安全)。

所有權

前面說過,同C/C++一樣,Rust中區分堆和棧(但不直接提及)。C/C++中語義上禁止從函數中返回指向局部變量的指針,是因爲當離開函數時,棧會被回收,所以局部變量也會被回收,導致指針失效。

在C/C++中,該禁止並非語法上的禁止。而在Rust中,所有權機制將該概念顯式表達,編譯器會拒絕編譯不滿足所有權機制的代碼。並且Rust設計者們希望通過所有權機制讓程序員不需要考慮堆棧,只需要考慮所有權即可(應當是出於一致性以及或許對未來自動優化的考量?)。

Rust中的 壽元 機制也涉及堆棧的區別。 兩者共同使得Rust不需要GC也可以保證內存安全。

所有權機制實際上就是三條規則:

  • 每個值具有唯一的所有者變量

  • 在同一時刻,所有者唯一

  • 所有者離開作用域時,值會被丟棄

    或者說(我個人的記法):每個值在每個時刻只有唯一的所有者,且有效性跟隨所有者。

所有權本身不涉及特殊語法,只要記住上面三條規則即可 …

理所當然地,Rust提供了條件、循環等控制流程。由於enum的特殊點(可以承載數據),Rust的條件結構爲其有專門的設計,所以也在此簡單介紹enum。

但壽元、所有權、引用借用等概念也影響enum的實際使用,故而會在Rust學習筆記/再敘enum和模式匹配再次討論。

條件結構

if 表達式

if 後接一個 bool 類型的表達式(不需要括號),且 ifelse 子句(均爲表達式)均需要大括號。特殊地,如要書寫 else-if ,則 else if 合併爲一個表達式。

...
let m = if a < 5 {
  println!("a小於5");
  4
} else if a < 8 {
  println!("a小於8 …

Rust提供元組和數組兩種複合數據類型。

元組

元組可將一系列數據(不需要是相同類型)“綁”在一起,以方便後續處理。

fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);

    let (x, y, z) = tup;

    println!("The value of y is: {}", y);

    let five_hundred = tup.0;

    let six_point_four = tup.1;

    let one = tup.2;
}

(抄自 官方教程元組部分

  • 使用 () 將多個數據合在一起組成元組
  • 元組的類型依賴於其各組成元素的數據類型
  • 解包操作可以一次性將元組中所有元素賦予某個值
  • 使用 .N …

類似於絕大多數語言,Rust核心部分提供的數據類型包含整型、實型(浮點數)等常規類型,同時也提供字符串類型。

標量/單量類型

整數與浮點數

i8 是8位(有符號)整型, i32 是32位整型; u32 是32位無符號整型; f32 是32位實型(浮點數)。 其具體大小均包括8、16、32、64這四種。 額外地,對於整數,另有 isizeusize 兩種,代表“取決於機器的大小”。

對於整數,Rust也支持許多其他“單位”的轉換:二進制、八進制、十六進制、以_分段的十進制。 額外地,對於 u8 ,Rust支持直接賦予ASCII對應值(如 b'A' )。例子及更多解釋見 官方教程整數類型部分

算數運算符和其他C家族的一致 …

C++有意使用concepts來改進模板使用中編譯期檢查效果(及生成易讀的報錯信息)。(同時該機制理論上亦可以用在普通的函數上。)

注意:concepts現在似乎並不是C++標準。

在目前的情況下,C++無法對模板參數進行任何限制,一切錯誤均需要由編譯器在具象化模板並處理到相應位置時纔能發現。而在其他一些語言中,可以通過對參數進行一定限制,達到提前發現問題(並且報錯更易檢查)。

  • 比如在Java中,可以對泛型參數進行 extendssuper 限定
  • 比如在Rust中,可以對模板參數所要滿足的trait進行限制

然而C++中完全沒有類似的機制,concepts就是用來解決該問題的方法。在上面兩例中,相比起Java,Rust對trait的使用和C++ concepts更爲相近。

Concepts聲明/定義了該模板參數所應當具有的函數,但又不要求一定是繼承某個類而來。該機制同時保留了靈活度,又使得報錯更可讀。(然而繼續增加編譯器複雜度,說不定導致編譯時間更長。)

包管理器Cargo的基本使用

Rust官方提供了包管理器Cargo,且Cargo同時負責項目的初始化、構建等功能。建議的做法是使用Cargo初始化項目,然後進行編寫,之後使用Cargo進行構建。故而,這裏簡單介紹如何用Cargo完成這兩項功能。

創建項目

$ cargo new YOUR_DIR --bin

該命令會在當前目錄下生成 YOUR_DIR 這個新目錄,其中包含新項目的“模板”文件(即示例)。之後的命令基本均在該目錄下進行。

注意這裏的 --bin 要求Cargo創建一個二進制/可執行項目; --lib 或留空均會創建庫項目。

YOUR_DIR 目錄下有一個名爲 Cargo.toml 的文件。該文件是項目的(Cargo)配置文件,用來描述項目的依賴關係、控制編譯/構建過程等。

編譯並執行項目

$ cargo run

Rust基本語法

Rust是一門強類型、手動類型的語言,這意味着函數參數需要顯式地聲明參數類型(以及返回值類型)。但Rust支持類型自動推導,在聲明並初始化變量時可以通過類型自動推導而省略變量類型 …