博客 / 詳情

返回

Rust 初步研究

趨勢

StackOverflow 公佈了報告 Rust 連續 7 年成為“最受歡迎”的語言。越來越多的公司準備在新項目上運用它來加強安全和性能上的保障: Gooogle、FaceBook、亞馬遜等大公司內部都有開始在底層系統上用 Rust 替代部分C/C++;最新的安卓 13 公佈了 SOAP 底層系統有 21% 的 Rust 程序,且數據顯示極大降低了安全漏洞;Linus 也表示 Rust 將成為 C 以外的另一種系統編程語言;華為、亞馬遜、微軟、Mozlia 等已成立 Rust 基金會。這一系列的事件表明,Rust 正在越來越被重視。

圖片

從另外一個角度,不同於一些被大公司把持的技術,Rust 是由公益組織 Mozilla 牽頭實驗的一項開源項目,整個過程和細節都是公開透明,且最終有多家公司成立的 Rust 基金會負責管理。 Rust 是一項沒有歷史包袱、且不被任何一家公司壟斷的技術。

源起

每種語言的誕生和受歡迎都有其背景,我們不比較語言的“好壞”,而是簡單看下 Rust 的是怎麼產生、想要解決什麼問題。

早在幾十年前的貝爾實驗室誕生的 C 及後續的 C ++ 提供了非常流行的高級語言,C 幾乎是為 unix 系統而生,是最高用高級語言編寫操作系統的嘗試之一。它提供了簡潔友好的上層表現能力,同時又能靈活的訪問底層。C ++ 理論上可以理解為 C 的一個超集(實際不完全兼容),在 C 的基礎上提供面向對象等各種解決複雜領域的抽象和工具。C/C++ 在誕生到如今,一直是底層系統開發的主流,操作系統、瀏覽器,嵌入式設備等這些需要訪問底層硬件,或者需要高性能的領域幾乎都在使用 C/C++。

C/C++ 相對彙編而言,是一種更高的抽象,但需要額外做很多底層的事情,比如控制指針操作堆內存,手動釋放內存等。對於大量上層應用軟件而言,我們希望開發者可以更加聚焦到業務本身,而不是底層細節。於是有了大量進一步抽象的高級語言,比如適合瀏覽器的 Javascript ,適合後台業務邏輯處理的 Java、C# , 流行於科學計算的 python 等。這些語言屏蔽了更多底層細節,做了更多適合於特定場景的設計,比如面向對象,弱化類型、函數式、垃圾回收等。這些抽象程度更高的語言,大部分都是解釋型語言,需要自帶解釋器或者虛擬機。解釋執行帶來的好處是跨平台,可以自動進行垃圾回收,代價是執行性能受到限制。

為了繼續在語言層面兼具性能和方便開發者,繼續優化方向有兩種。其一是把程序編譯成二進制文件,但同時仍然提供自動垃圾回收和各種高級抽象的語法,在性能和內存管理上進行平衡,代表則是 Google 發佈的 Go ;另一種是編譯成二進制文件,在不提供運行時的自動垃圾回收的情況,仍然能自動管理內存。

在 C/C++ 中,開發者需要手工管理內存,通常非常容易帶來潛在的安全問題,還有各種開發的心智負擔。Go 採取了折中,在編譯的二進制文件程序中,提供運行時垃圾回收。為什麼説是折中呢?因為內存沒有被開發者手動釋放,需要垃圾回收邏輯定期的尋找這些被引用的變量,然後再集中清理掉。在清理垃圾的過程,程序是處於短暫暫停狀態。對於大部分份場景,這種毫秒級暫停幾乎可以忽略不計,所以 Google 大部分後台業務由 Go 進行實現的。但對於更底層的操作系統、瀏覽器、FPS 遊戲、性能工具等對停滯非常敏感的場景,垃圾回收仍然被視為一種負擔。怎麼樣才能既滿足編譯到二進制的高性能,又不想增加開發管理內存帶來的心智負擔,

但又拒絕垃圾回收的開銷呢? Rust 正是被設計成這樣一種語言,用於滿足高性能,同時帶來現代化開發的安全可靠、效率便捷。

圖片

下面 Rust vs C/C++ 在不同計算類型下的基準性能測試對比 https://benchmarksgame-team.p...

圖片

Rust 怎麼做到的

在程序設計領域,相同資源下,想要增加某個方面的效益,必定要放棄另一個方面的效益,比如空間換時間,開發效率換執行效率等。 Rust 是用什麼資源置換免去手工管理內存的工作、同時保障內存安全可靠的?答案是額外的編譯。

C/C++ 中內存的不安全主要源於指針和堆上的內存,用户申請的堆內存可以指向任意一個指針,指針可以賦值給任意一個變量,多個變量指向同一塊堆內存,只要其中一個變量進行數據修改,內存釋放等行為,都會導致其他變量發生安全問題。

 char *pvalue = NULL;
 char *pvalue2 = NULL;
 char *pvalue3 = NULL;
 pvalue = (char *)malloc( 200 * sizeof(char) );
 strcpy(pvalue, "hello");
 pvalue2 = pvalue;
 pvalue3 = pvalue;
 strcpy(pvalue, "hello2");
 // ...
 free(pvalue)

如上面例子,同一個內存被指向多處變量,其中 pvalue 被修改,其他變量都會被自動改掉,這本質上是一種數據能夠被賦值共享修改的特性。軟件中的大量疑難雜症都和數據共享有關。

圖片

Rust 提供了一套內存安全的語法規則,保證在任意給定時間,要麼只能有一個引用被修改數據,要麼只能有多個不可變引用能讀取數據。這就是 Rust 變量所有權系統。

圖片

let mut s = String::from("hello");
 let r1 = &mut s;
 let r2 = &mut s;

以上看似“正常”的代碼編譯不會通過。所有權語法極大限制了開發者對變量的賦值處理,思維習慣和傳統類 C 語言完全不一樣,對開發者需要適應的時間成本會高,這就是我們通常説的,學會和 Rust 編譯器“鬥智鬥勇” 。

與之相連的另一個設計是借用檢查器,通常叫稱生命週期。

在 C/C++ 棧中的變量比如基礎數據類型是不需要手工釋放,函數棧被執行完畢,代表離開作用域,內存都會自動丟棄掉;如果一個函數返回了一個指針,該指針執行函數內部的變量,那指針指向的變量就會變成無效值;在更復雜的場景,比如指針經過層層傳遞到達某個邏輯,但指針執行的變量其實早就被釋放了。這種疑難雜症非常不好定位。

Rust 在編譯階段會用一些規則檢測引用是否有效,從而規避不安全的內存風險。作用域內有了對引用賦值的限制,Rust 編譯階段會自動在作用域結束階段插入內存釋放代碼,從而無需開發者手動寫清理邏輯,這和 C++ 中的智能指針的自動析構類似。

所有權、借用檢查是 Rust 以賦值靈活性為代價,通過編譯檢查換取內存安全。Rust 初學的難點基本圍繞在對這一套思維的適應。

現代化能力

作為一個大部分從事前端開發層的工程師,Rust 對我更具吸引力的其實是它超全的現代化能力。Rust 是一門提供高性能的同時,又提供了各種高級現代抽象的語言

工具

C/C++ 是“古老”的語言,因為各種各樣的歷史包袱,在工具鏈上沒法完全達成一致,我們需要依賴一個成熟富有經驗的工程師積攢各種“獨門工具集”。

Rust 不存在這個問題。統一的文檔、統一的構建工具、統一的包管理工具、統一的語言風格、統一的 RFC 等。你所需要的 Rust 工具集合,完全是像 web 前端的 node + npm 包一樣便捷。這對生態構建是非常友好的,庫工作者只需要一鍵安裝、專注在編碼本身,然後通過 cargo 交叉編譯不同系統下的二進制文件,再配合 npm 一鍵發佈到統一的包管理中心。

函數式、泛型、面向對象

Rust 在性能上定位於媲美 C/C++ ,但也把現代語法的各種便利設計到語言層面。迭代器、函數式、閉包、泛型(Rust 泛型加上生命週期會更復雜)、面向對象是前端領域經常使用的一些模式,非常容易適應這些能力。通常這些更高級的抽象模式,在其他語言中會損失性能,Rust 通過一些優化,極大規避了這種情況。比如針對泛型的“單態化”在編譯期自動填充類型;比如針對迭代器做的“零成本抽象”等。

讓我們簡單看下 Rust 對比前端 js/ts 的常用編程表現。(有些語法可省略)

• 閉包

閉包是函數表達式能作為參數,內部能捕獲外部作用域的變量。

rust版本

let x = 1;
let add_one = |y|  { return x + y };
let res = add_one(2);
println!("{}", res)

js/ts版本

let x = 1;
let add_one = (y) => {
  return x + y;
};
let res = add_one(2);
console.log!(res);

• 迭代器

迭代器是前端做數據轉化時常用的寫法

rust 版本

let v1: Vec<i32> = vec![1, 2, 3];

v1.iter().map(|x| {return x + 1});

js/ts版本

let v1: number[] = [1, 2, 3];
v1.map((x) => {return x + 1});

• 泛型

泛型是對不同數據類型的但行為相同的抽象,比如浮點數和整型都具備相加的行為,我們希望讓這兩個行為公用一個方法,而這個方法的類型則需要同時匹配多種類型

rust版本

use std::ops::Add;
fn main() {
let a = 1;
let b = 2;
let res = add(a, b);
println!("{}", res);
}

fn add<T:Add + Add<Output = T>>(a: T, b: T) -> T {
  return a + b
}

js/ts版本

ts 泛型比想象中其實更復雜,但實際開發中 any 容易被濫用。rust 泛型最複雜的部分是生命週期標註,且作為靜態編譯語言,rust 不存在 any 這樣的類型。

function main() {

let a = 1;
let b = 2;
let res = add(a, b);
console.log!(res);

}

function add<T extends number>(a: T, b: T): number {
  return a + b;
}

• 面向對象

有些人説 Rust 不是面向對象的語言,因為沒有繼承,這是非常狹隘的觀點。Rust 官方不僅對面向對象的本質做了定義,也為 Rust 為什麼不實現繼承做了解釋,面向對象只是一種實現封裝代碼,提高複用、可讀性的一種思想而已,但其實現的手段有很多種。按《設計模式》作者的觀點:面向對象的程序是由對象組成的。一個 對象 包含數據和操作這些數據的過程。這些過程通常被稱為 方法 或 操作。Rust 毫無疑問可以非常方便對數據做抽象。

trait Animal {
    fn say(&self) -> i32;
}
struct Dog {
    age: i32
}
impl Animal for Dog {
 fn say(&self) -> i32 {
        println!("{}", self.age);
        return self.age;
    }
}

rust 的面向對象主要通過結構體 + trait 組合而成,trait 有點像 ts 的接口、抽象類、class 的混合體。trait 就是用於抽象這些通共同特徵的東西,可以有默認實現、也能過被其他結構體實現,然後能作為參數約束泛型。Rust 中有大量暴露給開發者的官方 trait,定義好了藉口,需要開發者去實現,比如迭代器、Clone、Copy 等。

js/ts版本

abstract class Animal {
    abstract say():number;
}
class Dog extends Animal  {
    age: number
    say () {
      console.log(this.age)
      return this.age
    }
}

其他底層能力和無畏併發

自動清理內存、借用規則等是 Rust 安全的高級抽象,但 Rust 也暴露了和 C 一樣的底層能力。unsafe 關鍵字可以繞開這些限制,獲取裸指針,通常編寫底層代碼,或者提供更高性能的場景才會使用。Rust 包裝了一些常用的智能指針,比如向堆上寫數據的 Box<T>,允許相同數據有多個所有者的Rc<T>,用於併發的互斥鎖Mutex<T>等, 用Arc<T> 和 Mutex<T>的組合可以實現安全的在多線程之間共享所有權。 從底層能力的暴力可以看下 Rust 的安全理念就是:對於上層應用開發我們應該優先使用安全規則特性、而對常見的底層能力則通過智能指針包裝暴露給開發者,如果官方的智能指針無法滿足需求,則開發者通過 unsafe 自行實現底層能力。

未來

開篇已經講過,Rust 已經在諸如操作系統、瀏覽器、數據庫等大量涉及安全、性能的場景已經被各大公司在使用中。代表是亞馬遜、Google、華為等都是主要推動者,但想要短時間內把底層的基礎設施重構為 Rust 是可不能,未來會有更多公司對性能和的安全更敏感的底層會進行改造,在新項目上會做嘗試。

前端層面,Rust 對 Webassembly 支持非常給力,推進迅速。現代化齊全的包管理工具,配合前端 npm 包管理,在開發、測試、發佈過程都是無縫銜接。已有知名的前端項目有 swc 、 turbo 和 deno。前者替代 babel ,用 rust 重新編寫的 JS 轉譯器。turbo 是 webpack 作者的新項目,旨在用 Rust 編寫的打包器作為 webpack 繼任者。而 deno 則是 node 的作者目標是為 ts/js 實現一個更現代、更安全的運行時,用於替代 node 。

在跨平台上 Tauri 是一款和 electron 競爭的對手,採用 Rust 替代 Node 作為後端,系統自帶的 webview2 替代打包巨大的 chromium

Rust 可能是未來前端研發的新基礎設施。

——————
文檔信息標題:Rust 初步研究
發表時間:2022年12月4日
筆名:混沌福王
原鏈接:https://imwangfu.com/2022/12/...
版權聲明:如需轉載,請郵件知會 imwangfu@gmail.com,並保留此文檔信息申明
更多深度隨想可以關注公眾號:混沌隨想
——————

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.