posts - 110,  comments - 12,  trackbacks - 0

前言

其實我一直弄不明白一點,那就是計算機技術的發展,是讓這個世界變得簡單了,還是變得更復雜了。
當然這只是一個玩笑,可別把這個問題當真。

然而對于IT從業者來說,這可不是一個玩笑。幾乎每一次的技術發展,都讓這個生態變得更為復雜。“英年早禿”已經成為一種很普遍的現象。

Rust是近兩年呼聲比較高的一種新型開發語言。市場占有量并不大,但增長速度極為迅猛。
有人統計過,在計算機行業,平均每33.5天就有一種所謂的新型開發語言面世,這還不包括很多企業內部、項目內部的內置簡易流程工具。然而大浪淘沙,如今仍然占據著市場地位的,不過仍然是耳熟能詳的有限幾種。
作為新來的攪局者,Rust到底值不值得學習并且在工作中應用呢?

先說結論,這里粗略的把開發者分為初學者、小有經驗的常規工程師和資深開發者三類。
對于初學者,Rust具有比較陡峭的學習曲線,雖然學習Rust能訓練良好的編程習慣,從長遠看對提高學習者的開發素養極具價值。但短期的大量付出很容易讓初學者心力交瘁。并且盡管官方文檔并不欠缺,但學習資料對于初學者來講仍然是遠遠不夠的。所以比較而言,得不償失。因此建議初學者仍然由久經驗證的語言入門加入軟件開發的大家庭。比如說C/Java/Python/Js都是很好的入門選擇。
對于有一定經驗的常規工程師,他們已經有了一段時間的開發工作實踐,對于軟件開發的現狀、發展都已經形成了自己的世界觀。如果感覺并不很喜歡這個行業,希望將來轉行管理崗位或者產品崗位。那當前應當做的更多是傾向業務領域,了解業務和技術的銜接和互動,完全不需要學習Rust。而如果醉心于技術,并從中獲得了自己的樂趣,希望逐步提高自己的技術水平。那么Rust會是一個很好的橋梁,哪怕僅僅學習Rust而并不將其應用于工作,也能讓開發者從中獲取大量的有益習慣和軟件底層經驗,從而形成自己良好的代碼風格。
對于資深工程師,即便并不從而底層系統級的開發工作,Rust也是一門很優秀的語言。它能彌補當前多種開發語言的不足,形成良好的開發哲學和思想導向,幫助開發者交付高質量的軟件產品。因此,及早學習并應用Rust非常有價值。

為了說明這個結論,下面從多個角度,采用同傳統語言對比的方式來說一說我對Rust的理解。

Rust是一種全面創新的語言

這幾年有不少有影響的語言出現,但大多數都只是關鍵字或者小范圍的語法創新,隨后可能會有大量的特色庫函數來豐富語言的功能。一個有經驗的開發者,可能翻兩天資料,就能快速的掌握。
而Rust極具自身語言特點,是一種完全的創新,而不是簡單的語法替換。簡單的熟悉幾個關鍵字和判斷、循環等語法,遠不足以掌握這門語言。
為了證明這一點,下面用Rust的“所有權”(Ownership)機制和“遮蔽”(Shadowing)來舉例說明。

“所有權”機制(Ownership)和“遮蔽”(Shadowing)

以C++為例,請看下面這段代碼:

#include<iostream>
using namespace std;

int main(){
    string s1="hello";
    string s2=s1;
    cout << "s1=" << s1 << ",s2=" << s2 << endl;
    return 0;
}

編譯執行后,程序輸出:

s1=hello,s2=hello

代碼再簡單不過,首先聲明、賦值一個字符串變量s1,然后把變量s1賦值給變量s2,最后輸出兩者的值。

對應的,我們看一個Rust的版本:

fn main(){
    let s1=String::from("hello");
    let s2=s1;
    println!("s1={},s2={}",s1,s2);
}

除了細小的語法差異,看上去跟C++的版本沒有什么不同。然而在Rust中,這段代碼連編譯都無法通過,得益于rustc編譯程序詳細的輸出,我們能看到很細致的錯誤提示:

2 |     let s1=String::from("hello");
  |         -- move occurs because `s1` has type `std::string::String`, which does not implement the `Copy` trait
3 |     let s2=s1;
  |            -- value moved here
4 |     println!("s1={},s2={}",s1,s2);
  |                            ^^ value borrowed here after move

這個編譯錯誤是指,上面代碼中,當變量s1賦值給s2之后,s1變量名所指向的內存所有權,被“轉移”(move)到了s2變量名擁有之下。而從此之后,s1變量名就無效了,不再指向任何一塊內存。除非重新聲明并為s1賦值(Rust中稱為Shadow,"遮蔽"原有的s1),s1不能再被使用。
所有權機制可以有效的防止內存泄露所導致的程序Bug,是Rust內存管理的核心理念。上面提到的所有權“轉移”是所有權管理的重要特征之一。

“遮蔽”也是一個有趣的概念,Rust的處理方式跟很多我們熟悉的語言不同。
請看下面C語言代碼:

#include<stdio.h>

int main(){
    int x = 5;
    x = x+1;
    printf("x=%d\n", x);
}

這又是一段很基本的代碼。首先聲明、賦值一個整數變量x,接著把x的值加1,再賦值回變量x。這是各種開發語言中都常見的用法。編譯執行的輸出結果為x=6
來看看Rust的版本:

fn main(){
    let x=5;
    x = x+1;
    println!("x={}", x);
}

很不幸,這段代碼同樣無法編譯通過,錯誤是:

error[E0384]: cannot assign twice to immutable variable `x`
 --> test-own1.rs:3:5
  |
2 |     let x=5;
  |         -
  |         |
  |         first assignment to `x`
  |         help: make this binding mutable: `mut x`
3 |     x = x+1;
  |     ^^^^^^^ cannot assign twice to immutable variable

rustc這種“圖示”型的輸出信息讓你排查錯誤更加方便。
錯誤的原因,在Rust中,默認所有變量都是只讀類型的,除非在變量聲明的時候就注明為可變類型"mut"。
因此兩次對于一個只讀變量賦值導致編譯錯誤。
解決的辦法或者注明變量為可讀寫,這樣同C語言的版本具有完全相同的意義:

let mut x=5;

或者用我們上面提到過的“遮蔽”機制:

fn main(){
    let x=5;
    let x = x+1;
    println!("x={}", x);
}

注意上面x=x+1,這一行的開始我們再次使用let關鍵字,這表示再次聲明了變量x。
與大多數語言不允許重復聲明變量不同,這個x變量,跟第一次聲明的變量x同名,并對其做出了“遮蔽”。之后除非再次遮蔽變量x,那起作用的,都將是本次新聲明的x。

通過這兩個例子,可以看出Rust是從理念上做出了大量創新的一種語言。如果只是像學習其它語言一樣只是對比學習語法和關鍵字,無法真正掌握這門語言。這些融匯在語言中的理念,才是Rust最寶貴的地方。
注意在這里“理念”可不是什么大而化之的套話,而是實際操作中很重要的原則。
很多語言的設計初衷是“簡化”,在Rust中當然也有很多簡化的地方,就像直接使用“let”關鍵字聲明一個變量,而變量的類型可以通過賦值的操作從而推導出變量的類型。比如變量超出作用域,也會被自動的回收。
但Rust中也大量的存在了“復雜化”的操作,比如上面舉例的所有權機制,再比如使用可讀寫變量需要額外標注“mut”。
這些“復雜化”的部分,都基于“盡量在程序開發的早期,就將可能會出現問題的部分暴露出來,從而在設計中和編譯時就解決掉。”這樣一個理念。

引用(References)和借用(Borrowing)

承接自Rust的擁有權機制。引用和借用在Rust中也迥異于大量的傳統語言。
引用類似C語言中的指針,指向一塊已經存在的數據:

    let mut x = 5;
    let y = &x;

上例中,y就是對變量x的引用,并且沒有標注mut,所以是只讀引用。寫法跟C語言中獲取指針的方式類似,就是一個&符號。
y此時具有了變量x的一些權限,所以也稱為“借用”,本例中因為只借用了讀的功能,沒有借用寫的功能,所以稱“一些”。當然也可以借用寫的功能,我們后面會再舉例。
借用看起來跟引用是一回事,但“借用”這個詞更主要對應的是上面所說的所有權“轉移”的概念,轉移之后,原來的變量就無效了。而借用之后,原來的變量還有效,或者部分有效,比如只被借用了寫權限。

在函數參數中,使用引用的方式,從而讓函數臨時性的獲得數據的訪問權,也是典型的借用。事實上這種方式才是最常用到借用的地方:

fn main() {
    fn sum_vec(v: &Vec<i32>) -> i32 {
        return v.iter().fold(0, |a, &b| a + b);
    }
    let v1 = vec![1, 2, 3];
    let s1 = sum_vec(v1);
    println!("{}", s1);
}

先別管我們使用到的令人困惑的關鍵字和函數名,那些進入到系統學習之后都不算什么。在函數sum_vec的參數中,我們就使用了借用。
順便,我們還見識了Rust中函數的嵌套寫法,當然現在新興的語言,包括C++11之后的版本,都已經支持這種寫法,這在函數式(Functional programming paradigm,注意不是函數化Functionalization)編程中是很重要的支持。

引用和借用的概念,同C/C++語言中所使用的都是很類似的,盡管名稱不同。主要的區別來自于對引用的管理理念,Rust對引用的管理規則如下:

  • 對于一塊內存,同時只能有一個可寫引用存在
  • 對于一塊內存,同時可以有多個只讀引用存在
  • 對于一塊內存,在有一個可寫引用存在的時候,不能有其它引用存在,無論只讀或者可寫。
  • 引用的原始對象必須在引用存在的生命期一直有效

比如:

    let mut x = 5;
    let y = &mut x;
    let z = &mut x;
    println!("{} {} {}", x,y,z);    

上面代碼會產生編譯錯誤,因為y已經是可寫的引用,而同時再存在一個可寫的引用z,違反了Rust對引用的管理規則。
如果把z變量這一行和后面顯示z的部分去掉呢?去掉之后是可以編譯通過的,但仍然要注意,y此時是可寫的指針,“借用”了x的寫權限。所以x此時只有讀的權限,不能再對x進行賦值。因為它已經被“借用走”(Borrowed)了。

這些復雜的規則,看起來就跟前面見過的所有權轉移一樣,似乎極大的限制了程序員的自由度。
但這些都是在強迫你,讓你成為一個更優秀的程序員,產生出更高質量的代碼,將Bug消滅在萌芽期。

生命期(Lifetime)

通常一個變量的生命期就是它的作用域。但在引用和借用出現后,這個問題變得復雜了。
熟悉C語言的程序員都碰到過數據失效了,而指針依然存在的情況,俗稱“懸掛指針”。
Java為了解決這個問題干脆取消了指針,并且最終以引用計數器做為了內存管理的主要模式。

這種情況出現最多的場景,是在某個函數中使用了變量或者申請了內存,并將其引用作為返回值傳遞到了調用者的時候。比如這段C語言代碼:

int *getSomeData(){
    int c=32767;
    return &c;
}

c變量位于棧上,是一個局部變量,當函數返回指針的時候,指針在這個函數的調用者中依然存在,但c變量已經被回收了。
在新版本的編譯器中,這種情況也會被警告,但可以編譯成功。而在Rust中,這種情況是不允許編譯通過的,比如下面類似代碼:

fn somestr() -> &str {
    let result = String::from("a demo string");
    // 直接使用方法返回值(或者變量),之后沒有分號,
    // 是將其作為返回值處理,
    // 不用像c語言一樣return result.as_str()
    result.as_str()
}

編譯的時候會報錯“result變量沒有足夠長的生命期”:

error[E0597]: `result` does not live long enough
 --> src/main.rs:3:5
  |
3 |     result.as_str()
  |     ^^^^^^ does not live long enough
4 | }
  | - borrowed value only lives until here

如果僅僅是這樣斷然的禁止返回懸掛引用也就“不過如此”了。事實上更復雜的問題來自于,如果數據源來自于函數的參數,參數本身就是引用的情況。比如請看下面的Rust代碼:

fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x 
    } else {
        y
    }
}

上面這個函數接受兩個字符串的引用(實際是Slice,本文不是教學,請先忽略語法問題),比較其長度,將長的那個字符串作為結果返回調用者。順便,這種返回值的方式一定讓你印象深刻。
雖然示例簡單,但不可否認,這種需求是很正當的。大量的應用場景都需要函數獨立于外,處理固定的內存數據,進入和返回的,都只是指向內存的指針。
當然,盡管合理,上面的代碼是無法編譯通過的,報錯是“丟失生命期指定”:

error[E0106]: missing lifetime specifier
 --> src/main.rs:1:33
  |
1 | fn longest(x: &str, y: &str) -> &str {
  |                                 ^ expected lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but the
signature does not say whether it is borrowed from `x` or `y`

Rust引入了生命期的概念,從而保證返回值,同給定的參數,具有相同的生命期。這即保證了程序的靈活性,而又不造成內存的泄露,同時還不把維護內存安全的責任,完全推給不可靠的人為因素。

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

上面的代碼,添加了生命期指定。在函數名之后首先聲明了生命期a,語法樣式跟泛型的類型說明部分實際是一樣的,都放在尖括號<>之中。生命期名稱之前附加一個單引號'
隨后的兩個引用參數x/y以及作為返回值的字符串引用,都直接在&符號之后標注了生命期'a。這表示,這幾個引用,具有相同的生命期。
當然從這里的例子,x/y是調用的參數,是外面傳遞進來的,所以完整的含義應當是:返回的引用值,同參數x/y一樣具有相同的生命期。因此從調用者的角度來看,當x/y指向的內存,超出作用域銷毀之后,所獲得的函數返回值,也同時被銷毀。

有一個特殊的生命期'static,用于代表rust中的全局量或者靜態量,專門表示這種引用具有貫穿于整個程序運行時的生命期長度。
比如Rust中通常用字面量賦值的字符串,實際都是'static,因為這些字面量實際在程序編譯的時候就放置到了數據區并一直存在貫穿程序始終:

    let s = "I have a static lifetime.";

編譯時檢查和運行時開銷

通過前面的幾個個例子,我們對Rust的編譯器rustc有了一個初步概念。豐富、詳盡的編譯錯誤輸出對于排查源碼中的錯誤幫助很大。
實際上遠不止于此。Rust的編譯器包含著Rust語言的另外一個核心思想,那就是,盡量在編譯階段就暴露出程序的設計錯誤,而不讓這些錯誤帶到生產環境從而付出昂貴的代價。
這也是Rust學習曲線陡峭的原因之一,很多在其它語言中可以編譯通過的代碼,在Rust中都無法通過編譯(排除語法錯誤之外)。這種更嚴格的編譯時檢查很容易讓初學者手足無措。
帶來的優點也是顯而易見的,除了剛才提過的不讓程序Bug帶入到生產環境之外,錯誤能在編譯階段就消除掉,無需在運行時進行更多不必要的錯誤檢查,這也將大大的減少程序在運行時消耗。這個消耗包括編譯所生成的代碼體積和運行時檢查所損耗的CPU資源兩個方面。

比如Rust中有多種不同功能的智能指針,以常見的Box和Rc為例,前者提供基本的指針功能,后者提供類似Java語言一樣,基于引用統計的自動垃圾回收機制。
(請注意我們這里并不是做語言學習,所以請關注在Rust的設計理念上,先別在意具體的關鍵字和語法。)

如果在程序中使用Box指針的話,當變量x被賦值給變量y,所有權同時被轉移,變量x就不再可用了,這個我們在開始的所有權介紹時就見到了:

let x = Box::new(1);
let y = x;
// x從此無效了

與此規則對應的所有操作,在程序的編譯器就可以做出檢查,從而判斷是否有錯誤存在。
但畢竟我們也有其它的需求,比如我們希望同時有多個指針指向同一塊存儲區域。這時候就需要使用Rc指針。

let five = Rc::new(5);
let five1 = five.clone();
// 此時five/five1都是有效的

但顯然,使用Rc指針的時候,我們無法在編譯過程中發現可能的錯誤。并且,Rc指針類似Java,當對一塊內存的所有引用都失效之后,系統會釋放這部分內存。而這個過程,都需要在程序執行的過程中,有對應的管理代碼不停的工作,以保證跟蹤內存的引用和內存的釋放(垃圾回收)。這就產生了運行時開銷。

為了對運行時開銷能夠更精確的掌控,Rust在語言層面增加了許多選擇,這些選擇在其它語言中本來是不需要的。但一個經驗豐富的程序員,則能充分的利用這些不同的選擇寫出高品質的代碼。
比如Rc指針并不支持多線程,因為其中的引用計數器操作不是原子級的,所以Rust還提供了Arc用于多線程環境。當然,原子級的操作在運行時需要額外的開銷。

與Rust語言的編譯設計相映成趣的是Go,Go語言提供非常快速的編譯過程,從而提供流暢的開發體驗,讓Go語言易于學習和使用。但Go的編譯質量早就為人所詬病。
當然更極端的例子是Python、Js等腳本型的語言,腳本語言完全無需編譯。雖然執行效率方面這些年來隨著電腦性能的提高已經不是嚴重問題,但大多錯誤幾乎都只能通過代碼的執行來發現。使得腳本語言在商業軟件開發中占有率一直不高,更別說操作系統這一類的底層軟件了。

總結一下這一部分,Rust提供高級語言所具有的一些特征,比如自動的運行時垃圾回收機制。但同時也提供并且傾向于開發人員通過精細的設計,在開發和程序編譯過程中就完成內存的設計和管理,從而及早發現錯誤,降低運行時開銷,提高最終的代碼質量。

有限的面向對象特征

面向對象是現代開發語言的基本能力。但Rust只提供了有限的面向對象支持。
我衷心的認為這是一件好事,我一直認為現在很多的程序員,往往為了面向對象而去面向對象開發。把原本很簡單的事情做的過于復雜,使得代碼量和運行開銷高企不下,開發效率和執行效率完全失控。
Linus Torvalds曾經在那場著名的辯論中直呼C++是“糟糕程序員的垃圾語言”,有興趣的可以翻墻去看原文:Re: [RFC] Convert builin-mailinfo.c to use The Better String Library.

在Rust中沒有直接提供“類”(class)的概念,希望使用“對象”的程序員,可以直接在結構(struct)和枚舉(enum)類型上附加函數方法,比如:

// 聲明一個“圓”結構類型
struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}
// 為結構實現一個方法area
impl Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }
}

fn main() {
    let c = Circle { x: 0.0, y: 0.0, radius: 2.0 };
    // 調用結構的內置方法,計算圓的面積
    println!("{}", c.area());
}

看上去跟Go處理對象的方法很像是吧,其實在面向對象方面Go語言的理念也是高舉了“簡化”的大旗。
Rust也沒有我們習慣了的構造函數和析構函數。上面代碼中對Circle對象的初始化語句:

let c = Circle { x: 0.0, y: 0.0, radius: 2.0 };

就是直接對成員變量的賦值。
這是因為Rust推崇“明確化”(being explicit)的代碼方式,也就是所有要執行的代碼,應當清晰的在代碼中體現出來。而不是隱藏在一些容易忘記、容易出錯的構造函數之后。

與“簡化對象”相反的,Rust對面向對象中“接口”(Java中的interface,或者C++中的多重繼承)的概念做了發揚,貫穿在了Rust類型管理的方方面面。
當然我這樣說有點不算貼切,其實應當先忘記“接口”的概念,從頭理解Rust中的“特質”(trait),因為特質和接口,只是在技術實現上有些類似,但在應用理念上還是很有區別的。
本質上說,“特質”也是實現多個對象中,共性的方法,比如:

trait HasArea { //求取對象的面積
    fn area(&self) -> f64;
}

隨后多個對象,都可以實現這個特質,從而都具有這個方法:

struct Circle { //定義一個“圓”對象
    x: f64,
    y: f64,
    radius: f64,
}

impl HasArea for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }
}

struct Square { //”定義一個“方形”對象
    x: f64,
    y: f64,
    side: f64,
}

impl HasArea for Square {
    fn area(&self) -> f64 {
        self.side * self.side
    }
}

在Rust中,通過泛型的幫助,根據數據類型實現的不同特質,把類型分為不同的功能和用途。
比如具有“Send”特質的類型,才可以安全的在多個線程間傳遞從而共享數據。
比如具有“Copy”特質的類型,說明數據保存在棧(Stack)上,數據的復制(賦值給其它變量),不會產生所有權的轉移(參考前面所有權的例子)。
還有比如,剛才說過了Rust中沒有析構函數,但如果有一些數據并沒有被Rust所管理,需要自己去釋放,則可以為自己定義的對象實現一個Drop特質,在其中的drop方法中釋放自己申請的內存:

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

其它面向對象的編程特征,比如“泛型”,比如“重載”,同其它語言并沒有很大的區別,這里不再額外介紹。
這些相比較其它面向對象語言而言,并不算豐富的語法工具,是保留了面向對象開發模式最精華的部分。并不會對業務的描述造成什么障礙,反而會讓建模工作更為簡潔、務實,盡可能不造成代碼上的晦澀和運行時的低效。

內置的綜合管理工具

早期出現的開發語言,比如C,比如Java,本身并沒有附加官方的管理工具。比如包管理、測試管理、編譯管理。
在語言的發展過程中,因為開發工作的需求,往往會出現多個有影響的工具。在C/C++方面,常見的編譯管理工具有Makefile/CMake/AutoMake等,包管理工具,往往同系統包管理工具結合在一起,常見的有APT/YUM/Aptitude/Dnf/HomeBrew。Java的情況也很類似。
新近風靡的語言,比如Python,Pip工具占了大部分市場。Nodejs則是NPM用戶最多。Go語言的同名管理工具就更不用說了。這些現象,跟語言本身的官方支持密不可分。
Rust也由官方直接發布Cargo工具,功能涵蓋版本升級、項目管理、包管理、測試管理、編譯管理等多方面。
大多數初學者的Rust之旅,就是由執行cargo new helloworld開始的。
開發語言的綜合管理工具,對于構建大型的軟件項目必不可少。相信在cargo的幫助下,讓學習者快速的學以致用,把一些項目遷移至Rust能輕松不少。

擴展庫支持

一門語言能否被大量用戶支持,與語言所提供的擴展庫功能密不可分。
我就見到不少程序員學習Python的原因,就是因為Python能夠更好的支持PyTorch / TensorFlow等機器學習工具包。
Rust通過Crate(可以翻譯為擴展箱)機制支持自己的擴展包。而且通過內置的Cargo工具可以直接使用大量的官方預置擴展包和社區共享的擴展包。
此外Rust還可以通過FFI接口(Foreign Function Interface)直接調用其它語言編寫的函數庫或者共享Rust函數給其它語言調用。
比如我們在Rust中調用c++寫的snappy壓縮、解壓功能包。Snappy官方網站為:https://google.github.io/snappy/,在macOS下安裝Snappy包可以使用命令:brew install snappy

extern crate libc;
use libc::size_t;

// 下面這條宏命令,表示緊接著的extern塊中的函數,在snappy庫中鏈接,
// rustc會自動鏈接libsnappy.dylib庫
// 如果是linux則是鏈接libsnappy.so文件
// 如果使用宏命令:#[link(name = "snappy",kind = "static")]
// 則會鏈接libsnappy.a靜態庫文件
#[link(name = "snappy")]
extern {
    // 調用函數僅為示例,本函數估算所給定尺寸的數據,
    // 壓縮后最大的長度,供用戶申請輸出緩存空間
    fn snappy_max_compressed_length(source_length: size_t) -> size_t;
}

fn main() {
    // 對于外部的非Rust語言庫函數調用,需要在unsafe塊中調用
    let x = unsafe { snappy_max_compressed_length(100) };
    println!("max compressed length of a 100 byte buffer: {}", x);
}

因為使用了libc擴展庫,需要在Cargo.toml中設置庫依賴:

[dependencies]
libc = "0.2.0"

編譯的時候,rustc會自動鏈接libc庫和宏定義指明的snappy壓縮解壓庫。

把Rust中定義的函數,共享給c語言調用也很類似,請看Rust代碼:

extern crate libc;
use libc::uint32_t;

#[no_mangle]
pub extern fn add(a: uint32_t, b: uint32_t) -> uint32_t {
    a + b
}

上面的代碼,需要設置Cargo.toml文件的lib參數:

[lib]
crate-type =["cdylib"]

從而讓rustc將項目編譯為.dylib動態鏈接庫文件(macOS)或者.so動態鏈接庫文件(Linux)。
對應的C語言代碼:

#include <stdint.h>
extern "C" uint32_t add(uint32_t, uint32_t);

int main(){
    uint32_t sum = add(5, 5);
    return 0;
}

C代碼編譯的時候,記著使用-l參數鏈接rust生成的動態鏈接庫。
綜上,遷移至Rust完全不用擔心擴展庫的限制,也完全不用擔心同現有軟件資源之間的互動、共享。可以從一個小的項目作為切入點,邊學邊用,在享受Rust安全可靠的同時,逐漸達成軟件架構的遷移。

Rust是一種可以進行底層開發的高級語言

現在流行的開發語言很多,但能夠進行操作系統底層開發的選擇項并沒有幾個。
除了傳統的C、新近的Go,Rust是另一個不錯的選擇。
做到這一點,除了Rust是真正的二進制編譯之外,Rust還具有非常小并且可控的“腳印”(footprint)。這代表Rust可以做到完全沒有自己的運行時庫支持下運行。
比如官方文檔中提供的一個例子:

#![feature(lang_items)]
#![feature(start)]
#![no_std]

// Pull in the system libc library for what crt0.o likely requires
extern crate libc;

// Entry point for this program
#[start]
fn start(_argc: isize, _argv: *const *const u8) -> isize {
    0
}

// These functions are used by the compiler, but not
// for a bare-bones hello world. These are normally
// provided by libstd.
#[lang = "eh_personality"]
#[no_mangle]
pub extern fn rust_eh_personality() {
}

// This function may be needed based on the compilation target.
#[lang = "eh_unwind_resume"]
#[no_mangle]
pub extern fn rust_eh_unwind_resume() {
}

#[lang = "panic_fmt"]
#[no_mangle]
pub extern fn rust_begin_panic(_msg: core::fmt::Arguments,
                               _file: &'static str,
                               _line: u32) -> ! {
    loop {}
}

其中#![no_std]宏代碼就表示本代碼編譯時不使用rust標準庫。
想要真正從頭編寫一個操作系統,這個話題還是比較大,有興趣的可以參考一下這個博客:https://os.phil-opp.com/作者Philipp Oppermann循序漸進的演示用Rust在沒有標準庫甚至沒有libc庫的支持下從頭開始編寫一個操作系統,該博客提供了一個學習性的實現。

其它

作為新興的開發語言,Rust在函數式編程、網絡編程、多線程、消息同步、鎖、測試代碼、異常處理等方面都有不俗表現。但本文不是Rust教學,所以這里不再介紹。建議在學習Rust的過程中,根據所選教程的組織結構來逐步了解。

企業應用中,Web框架和ORM是最常用到的組件,但這應當說是Rust當前的一個短板。
因為畢竟Rust是一個新興的生態系統,盡管選擇很多,但尚沒有重量級的選手出現。在性能和規模化的應用方面還有待市場驗證。
但Rust本身對內存、性能的精細管理,讓我們可以對項目的總體性能保持信心。
Actix-web、hyper的web框架,以及Diesel作為ORM是比較常見的組合。

小結

Rust首先包含了長期軟件工程中對于高頻Bug的經驗總結,從而開創性的提出了大量的全新編程理念。
不同于很多新式語言給予開發者更多的便利和自由,Rust更苛刻的對待程序員的開發工作......盡管在易用方面Rust也下了不少的功夫,但相對于繁復的規則,這些努力很容易被忽視。
而這些“成長的代價”保證了更高品質的開發輸出。

比如自2004年以來,微軟安全響應中心(MSRC)已對所有報告過的微軟安全漏洞進行了分類。根據他們提供的數據,所有微軟年度補丁中約有 70%是針對內存安全漏洞的修復程序。恐怕沒有人再繼續做延伸統計,比如這些安全漏洞造成了多少的經濟損失。
所以甚至已有傳聞微軟正在探索使用Rust編程語言作為 C、C++和其他語言的替代方案,以此來改善應用程序的安全狀況。

Rust并不適合初學者,只有經歷過大量實踐磨煉,甚至被安全漏洞痛苦折磨的資深開發者,才能更理解Rust的價值。

自由還是安全,終要有所取舍。

posted on 2019-10-13 12:04  俺踏月色而來  閱讀(...)  評論(... 編輯 收藏

ag二分彩