連續 3 年最受歡迎:Rust,香!

雲棲號資訊:【點擊查看更多行業資訊
在這裏您可以找到不同行業的第一手的上雲資訊,還在等什麼,快來!

image

阿里妹導讀:我們在選擇一種開發語言時會綜合考量各方面的特性,根據實際的需求適當取捨。魚和熊掌往往不可兼得,要想開發效率高,必然要犧牲性能和資源消耗,反之亦然。但是Rust卻出其不意,令人眼前一亮!本文將從性能、內存安全、開發效率、跨平臺性及生態等五個方面,對Rust這一編程語言進行一些科普性質的分享。

一 性能對比

不同的語言使用不同的內存管理方式,一些語言使用垃圾回收機制在運行時尋找不再被使用的內存並釋放,典型的如Java、Golang。在另一些語言中,程序員必須親自分配和釋放內存,比如C/C++。Rust 則選擇了第三種方式:內存被一個所有權系統管理,它擁有一系列的規則使編譯器在編譯時進行檢查,任何所有權系統的功能都不會導致運行時開銷。Rust 速度驚人且內存利用率極高,標準Rust性能與標準C++性能不相上下,某些場景下效率甚至高於C++。由於沒有運行時和垃圾回收,它能夠勝任對性能要求特別高的服務。網上已經有了很多關於Rust性能分析對比的文章,不過爲了獲得一手的資料,還是自己動手來的更加真實。我選擇了Python,C++,Golang這3種語言來和Rust做性能對比。

性能測試場景設計

同樣的算法用4種語言分別實現,對比在規定的時間內完成任務的次數。本次測試選擇的算法是找出10000000以內的所有素數,比較在一分鐘內完成找出所有素數任務的次數。

源代碼鏈接見[1]。

靜態編譯(或者打包)後生成的二進制大小對比

image

結論:(二進制大小)python > golang > rust > c++

運行速度對比

本場景下比較1分鐘內找出1000000以內所有素數的次數。

image

結論:(運行效率)rust > c++ > golang > python

重點來了,在3臺不同的機器上測試四次的結果顯示:Rust效率居然高於C++!!!

內存消耗對比(粗略計算)

image

結論:(內存消耗) python > golang > rust > c++

CPU消耗對比(粗略計算)

image

結論:(CPU消耗)golang > python > rust = c++

以上便是我的測試結果,測試代碼、二進制和測試結果參考附件bin.zip,第一次測試後看到結果,有些喫驚,rust的性能居然超過了c++,不可思議,於是又在網上搜索,找到了別人已經完成的rust性能測試,網上的結果更讓人喫驚,先看第一篇,原始鏈接見[2]。

我直接截圖看結論:

image


image

以上爲Rust vs Golang。

image


image

以上爲Rust vs C++。

結論:以上截圖顯示,Rust在性能和資源消耗上不僅大幅度優於Golang,並且和C++性能不相上下,某些場景下效率甚至優於C++。

以上兩種測試場景只是測試一些簡單的算法,接下來我們看一下在實際使用中的性能資源佔用對比,依然是在網上找到了一篇測試報告[3],該測試報告用Python、PyPy、Go、Rust四種語言實現了一個web後端,接下來使用wrk分別對四個http服務器進行壓測,該測試場景比較貼近實際,直接截圖看結論:

image


image


image


image

結論(性能):在實際作爲後端服務使用的場景下,Rust比Golang依然有明顯性能優勢。

image

結論(資源佔用):在內存佔用上Rust的優勢更加明顯,只用了Golang的1/3。

綜合以上3個測試,Rust在運行效率和資源消耗上的優勢十分明顯,和C++同一個級別,遠遠優於Golang !

二 內存安全性

Rust 最重要的特點就是可以提供內存安全保證,而且沒有額外的性能損失。在傳統的系統級編程語言( C/C++) 的開發過程中,經常出現因各種內存錯誤引起的崩潰或bug ,比如空指針、野指針、內存泄漏、內存越界、段錯誤、數據競爭、迭代器失效等,血淚斑斑,數不勝數;內存問題是影響程序穩定性和安全性的重大隱患,並且是影響開發效率的重大因素;根據google和微軟 兩大巨頭的說法,旗下重要產品程序安全問題70%由內存問題引發[4], 並且兩個巨頭都用利用Rust語言來解決內存安全問題的想法。Rust語言從設計之初就把解決內存安全作爲一個重要目標,通過一系列手段保證內存安全,讓不安全的潛在風險在編譯階段就暴露出來。接下來根據自己粗淺的理解,簡單介紹Rust解決內存安全的手段有哪些。

1 所有權規則

1)Rust 中每一個值或者對象都有一個稱之爲其 所有者(owner)的變量。

例如:

let obj = String::from("hello");

obj是String對象的所有權變量。

2)值或對象有且只能有一個所有者。

3)當所有者離開作用域,所有者所代表的對象或者值會被立即銷燬。

4)賦值語句、函數調用、函數返回等會導致所有權轉移,原有變量會失效。

例如:

fn main() {
    let s = String::from("hello");
    let s1 = s; //所有權發生了轉移,由s轉移給s1
    print!("{}",s); //s無效,不能訪問,此句編譯會報錯
}

fn test(s1:String){
    print!("{}",s1);
}

fn main() {
    let s = String::from("hello");
    test(s); //傳參,所有權發生了轉移
    print!("{}",s); //此處s無效,編譯報錯
}

Rust的所有權規則保證了同一時刻永遠只有一個變量持有一個對象的所有權,避免數據競爭。

2 借用規則

可能大家都發現了問題,什麼鬼,爲什麼我傳了個參數s給test函數,這參數s後面還不能用了呢?如果我接下來要使用變量s怎麼辦?這時候就要用到Rust的借用特性。在Rust中,你擁有一個變量的所有權,如果想讓其它變量或者函數訪問,你可以把它“借”給其它變量或者你所調用的函數,供它們訪問。Rust會在編譯時檢查所有借出的值,確保它們的壽命不會超過值本身的壽命。

例如,以下的寫法就沒有問題:

fn test(s1:&String){
    print!("{}",s1);
}

fn main() {
    let s = String::from("hello");
    test(&s); //傳參,注意只是傳遞了引用,所有權還歸屬於s
    print!("{}",s); //此處s依然有效,可以訪問
}

fn main() {
    let s = String::from("hello");
    let s1 = &s; //s1借用s,所有權還歸屬於s
    print!("{}",s); //此處s依然有效,可以訪問
    print!("{}",s1); //此處s1和s指向同一個對象
}

如果我們嘗試修改借用的變量呢?

fn main() {
    let s = String::from("hello");
    change(&s);

}

fn change(some_string: &String) {
    some_string.push_str(", world");
}

借用默認是不可變的,上面的代碼編譯時會報錯:

error[E0596]: cannot borrow immutable borrowed content `*some_string` as mutable
 --> error.rs:8:5
  |
7 | fn change(some_string: &String) {
  |                        ------- use `&mut String` here to make mutable
8 |     some_string.push_str(", world");
  |     ^^^^^^^^^^^ cannot borrow as mutable

根據編譯錯誤的提示,通過mut關鍵字將默認借用修改爲可變借用就OK,如下代碼可以編譯通過:

fn main() {
    let mut s = String::from("hello");
    change(&mut s);

}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

不過可變引用有一個很大的限制:在特定作用域中的特定數據有且只能有一個可變引用,這個限制的好處是 Rust 可以在編譯時就避免數據競爭,這些代碼會失敗:

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

報錯如下:

error[E0499]: cannot borrow `s` as mutable more than once at a time
 --> borrow_twice.rs:5:19
  |
4 |     let r1 = &mut s;
  |                   - first mutable borrow occurs here
5 |     let r2 = &mut s;
  |                   ^ second mutable borrow occurs here
6 | }
  | - first borrow ends here

在存在指針的語言中,容易通過釋放內存時保留指向它的指針而錯誤地生成一個 懸垂指針(dangling pointer),所謂懸垂指針是其指向的內存可能已經被分配給其它持有者或者已經被釋放。相比之下,在 Rust 中編譯器確保引用永遠也不會變成懸垂狀態:當我們擁有一些數據的引用,編譯器確保數據不會在其引用之前離開作用域。

讓我們嘗試創建一個懸垂引用,Rust 會通過一個編譯時錯誤來避免:

fn main() {
    let reference_to_nothing = dangle();

}

fn dangle() -> &String {
    let s = String::from("hello");
    &s
}

這裏是編譯錯誤:

error[E0106]: missing lifetime specifier
 --> dangle.rs:5:16
  |
5 | fn dangle() -> &String {
  |                ^ expected lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there is
  no value for it to be borrowed from
  = help: consider giving it a 'static lifetime

讓我們簡要的概括一下之前對引用的討論,以下3條規則在編譯時就會檢查,違反任何一條,編譯報錯並給出提示。

1)在任意給定時間,只能 擁有如下中的一個:

  • 一個可變引用。
  • 任意數量的不可變引用。

2)引用必須總是有效的。

3)引用的壽命不會超過值本身的壽命。

3 變量生命週期規則

生命週期檢查的主要目標是避免懸垂引用,考慮以下示例 中的程序,它有一個外部作用域和一個內部作用域,外部作用域聲明瞭一個沒有初值的變量 r,而內部作用域聲明瞭一個初值爲 5 的變量 x。在內部作用域中,我們嘗試將 r 的值設置爲一個 x 的引用。接着在內部作用域結束後,嘗試打印出 r 的值:

error[E0106]: missing lifetime specifier
 --> dangle.rs:5:16
  |
5 | fn dangle() -> &String {
  |                ^ expected lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there is
  no value for it to be borrowed from
  = help: consider giving it a 'static lifetime

當編譯這段代碼時會得到一個錯誤:

error: `x` does not live long enough
   |
6  |         r = &x;
   |              - borrow occurs here
7  |     }
   |     ^ `x` dropped here while still borrowed
...
10 | }
   | - borrowed value needs to live until here

編譯錯誤顯示:變量 x 並沒有 “活的足夠久”,那麼Rust是如何判斷的呢?

編譯器的這一部分叫做 借用檢查器(borrow checker),它比較作用域來確保所有的借用都是有效的。如下:r 和 x 的生命週期註解,分別叫做 'a 和 'b:

{
    let r;                // -------+-- 'a
                          //        |
    {                     //        |
        let x = 5;        // -+-----+-- 'b
        r = &x;           //  |     |
    }                     // -+     |
                          //        |
    println!("r: {}", r); //        |
}                         // -------+

我們將 r 的生命週期標記爲 'a 並將 x 的生命週期標記爲 'b。如你所見,內部的 'b 塊要比外部的生命週期 'a 小得多。在編譯時,Rust 比較這兩個生命週期的大小,並發現 r 擁有生命週期 'a,不過它引用了一個擁有生命週期 'b 的對象。程序被拒絕編譯,因爲生命週期 'b 比生命週期 'a 要小:被引用的對象比它的引用者存在的時間更短。

關於借用生命週期檢查,Rust還有一套複雜的生命週期標記規則,使Rust能在編譯時就能發現可能存在的懸垂引用,具體鏈接見[5]。

4 多線程安全保證

內存破壞很多情況下是由數據競爭(data race)所引起,它可由這三個行爲造成:

  • 兩個或更多指針同時訪問同一數據。
  • 至少有一個這樣的指針被用來寫入數據。
  • 不存在同步數據訪問的機制。

那麼在多線程環境下,Rust是如何避免數據競爭的?

先從一個簡單的例子說起,嘗試在另一個線程使用主線程創建的 vector:

use std::thread;
fn main() {
    let v = vec![1, 2, 3];
    let handle = thread::spawn(|| {
        println!("Here's a vector: {:?}", v);
    });
    handle.join().unwrap();
}

閉包使用了 v,所以閉包會捕獲 v 並使其成爲閉包環境的一部分。因爲 thread::spawn 在一個新線程中運行這個閉包,所以可以在新線程中訪問 v。然而當編譯這個例子時,會得到如下錯誤:

error[E0373]: closure may outlive the current function, but it borrows `v`,
which is owned by the current function
 --> src/main.rs:6:32
  |
6 |     let handle = thread::spawn(|| {
  |                                ^^ may outlive borrowed value `v`
7 |         println!("Here's a vector: {:?}", v);
  |                                           - `v` is borrowed here
  |
help: to force the closure to take ownership of `v` (and any other referenced
variables), use the `move` keyword
  |
6 |     let handle = thread::spawn(move || {
  |                                ^^^^^^^

Rust 會“推斷”如何捕獲 v,因爲 println! 只需要 v 的引用,閉包嘗試借用 v。然而這有一個問題:Rust 不知道這個新建線程會執行多久,所以無法知曉 v 的引用是否一直有效。所以編譯器提示:
closure may outlive the current function, but it borrows v

下面展示了一個 v 的引用很有可能不再有效的場景:

use std::thread;
fn main() {
    let v = vec![1, 2, 3];
    let handle = thread::spawn(|| {
        println!("Here's a vector: {:?}", v);
    });
    drop(v); // 強制釋放變量v
    handle.join().unwrap();
}

爲了修復示上面的編譯錯誤,我們可以聽取編譯器的建議:

help: to force the closure to take ownership of `v` (and any other referenced
variables), use the `move` keyword
  |
6 |     let handle = thread::spawn(move || {

接下來是正確的寫法:

use std::thread;
fn main() {
    let v = vec![1, 2, 3];
    let handle = thread::spawn(move || {  //使用 move 關鍵字強制獲取它使用的值的所有權,接下來就可以正常使用v了
        println!("Here's a vector: {:?}", v);
    });
    handle.join().unwrap();
}

從上面簡單例子中可以看出多線程間參數傳遞時,編譯器會嚴格檢查參數的生命週期,確保參數的有效性和可能存在的數據競爭。

大家注意到沒有,上面的例子雖然能正確編譯通過,但是有個問題,變量v的所有權已經轉移到子線程中,main函數已經無法訪問v,如何讓main再次擁有v呢?如果用C++或者Golang等語言,你可以有很多種選擇,比如全局變量,指針,引用之類的,但是Rust沒有給你過多的選擇,在Rust中,爲了安全性考慮,全局變量爲只讀不允許修改,並且引用不能直接在多線程間傳遞。Rust 中一個實現消息傳遞併發的主要工具是 通道(channel),這種做法時借鑑了Golang的通道,用法類似。

示例:

use std::thread;
use std::sync::mpsc;
fn main() {
    let (tx, rx) = mpsc::channel();
    thread::spawn(move || {
        let val = String::from("hi");
        tx.send(val).unwrap();
    });
    let received = rx.recv().unwrap();
    println!("Got: {}", received);
}

上例中,我們可以在main函數中通過channel得到了子線程中的對象val。

注意,tx.send(val).unwrap(); 之後,val的所有權已經發生了變化,接下來在子線程中不能再對val進行操作,否則會有編譯錯誤,如下代碼:

use std::thread;
use std::sync::mpsc;
fn main() {
    let (tx, rx) = mpsc::channel();
    thread::spawn(move || {
        let val = String::from("hi");
        tx.send(val).unwrap();
        println!("val is {}", val);//在這裏會發生編譯錯誤
    });
    let received = rx.recv().unwrap();
    println!("Got: {}", received);
}

這裏嘗試在通過 tx.send 發送 val 到通道中之後將其打印出來。允許這麼做是一個壞主意:一旦將值發送到另一個線程後,那個線程可能會在我們再次使用它之前就將其修改或者丟棄。這會由於不一致或不存在的數據而導致錯誤或意外的結果。對於上面的代碼,編譯器給出錯誤:

error[E0382]: use of moved value: `val`
  --> src/main.rs:10:31
   |
9  |         tx.send(val).unwrap();
   |                 --- value moved here
10 |         println!("val is {}", val);
   |                               ^^^ value used here after move
   |
   = note: move occurs because `val` has type `std::string::String`, which does
not implement the `Copy` trait

我們通過channel能夠實現多線程發送共享數據,但是依然有個問題:通道一旦將一個值或者對象send出去之後,我們將無法再使用這個值;如果面對這樣一個需求:將一個計數器counter傳給10條線程,每條線程對counter加1,最後在main函數中彙總打印出counter的值,這樣一個簡單的需求如果使用C++或者Golang或者其它非Rust語言實現,非常容易,一個全局變量,一把鎖,幾行代碼輕鬆搞定,但是Rust語言可就沒那麼簡單,如果你是一個新手,你可能會經歷如下“艱難歷程”:

首先很自然寫出第一版:

use std::sync::Mutex;
use std::thread;
fn main() {
    let counter = Mutex::new(0);
    let mut handles = vec![];
    for _ in 0..10 {
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }
    for handle in handles {
        handle.join().unwrap();
    }
    println!("Result: {}", *counter.lock().unwrap());
}

多線程有了,Mutex鎖也有了,能保證每一次加一都是原子操作,代碼看起來沒什麼問題,但是編譯器會無情報錯:

error[E0382]: capture of moved value: `counter`
  --> src/main.rs:10:27
   |
9  |         let handle = thread::spawn(move || {
   |                                    ------- value moved (into closure) here
10 |             let mut num = counter.lock().unwrap();
   |                           ^^^^^^^ value captured here after move
   |
   = note: move occurs because `counter` has type `std::sync::Mutex<i32>`,
   which does not implement the `Copy` trait
error[E0382]: use of moved value: `counter`
  --> src/main.rs:21:29
   |
9  |         let handle = thread::spawn(move || {
   |                                    ------- value moved (into closure) here
...
21 |     println!("Result: {}", *counter.lock().unwrap());
   |                             ^^^^^^^ value used here after move
   |
   = note: move occurs because `counter` has type `std::sync::Mutex<i32>`,
   which does not implement the `Copy` trait
error: aborting due to 2 previous errors

錯誤信息表明 counter 值的所有權被move了,但是我們又去引用了,根據所有權規則,所有權轉移之後不允許訪問,但是爲什麼會發生?

讓我們簡化程序來進行分析。不同於在 for 循環中創建 10 個線程,僅僅創建兩個線程來觀察發生了什麼。將示例中第一個 for 循環替換爲如下代碼:

let handle = thread::spawn(move || {
    let mut num = counter.lock().unwrap();
    *num += 1;
});
handles.push(handle);
let handle2 = thread::spawn(move || {
    let mut num2 = counter.lock().unwrap();
    *num2 += 1;
});
handles.push(handle2);

這裏創建了兩個線程並將用於第二個線程的變量名改爲 handle2 和 num2,編譯會給出如下錯誤:

error[E0382]: capture of moved value: `counter`
  --> src/main.rs:16:24
   |
8  |     let handle = thread::spawn(move || {
   |                                ------- value moved (into closure) here
...
16 |         let mut num2 = counter.lock().unwrap();
   |                        ^^^^^^^ value captured here after move
   |
   = note: move occurs because `counter` has type `std::sync::Mutex<i32>`,
   which does not implement the `Copy` trait
error[E0382]: use of moved value: `counter`
  --> src/main.rs:26:29
   |
8  |     let handle = thread::spawn(move || {
   |                                ------- value moved (into closure) here
...
26 |     println!("Result: {}", *counter.lock().unwrap());
   |                             ^^^^^^^ value used here after move
   |
   = note: move occurs because `counter` has type `std::sync::Mutex<i32>`,
   which does not implement the `Copy` trait
error: aborting due to 2 previous errors

啊哈!第一個錯誤信息中說,counter 所有權被移動進了 handle 所代表線程的閉包中。因此我們無法在第二個線程中再次捕獲 counter , Rust 告訴我們不能將 counter 的所有權移動到多個線程中。所以錯誤原因明朗了,因爲我們在循環中創建了多個線程,第一條線程獲取了 counter 所有權後,後面的線程再也拿不到 counter 的所有權。如何讓多條線程同時間接(注意,只能是間接)擁有一個對象的所有權,哦,對了,引用計數!

通過使用智能指針 Rc 來創建引用計數的值,嘗試使用 Rc 來允許多個線程擁有 Mutex 於是寫了第二版:

use std::rc::Rc;
use std::sync::Mutex;
use std::thread;
fn main() {
    let counter = Rc::new(Mutex::new(0));
    let mut handles = vec![];
    for _ in 0..10 {
        let counter = Rc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }
    for handle in handles {
        handle.join().unwrap();
    }
    println!("Result: {}", *counter.lock().unwrap());
}

再一次編譯並…出現了不同的錯誤!編譯器真是教會了我們很多!

error[E0277]: the trait bound `std::rc::Rc<std::sync::Mutex<i32>>:
std::marker::Send` is not satisfied in `[closure@src/main.rs:11:36:
15:10
counter:std::rc::Rc<std::sync::Mutex<i32>>]`
  --> src/main.rs:11:22
   |
11 |         let handle = thread::spawn(move || {
   |                      ^^^^^^^^^^^^^ `std::rc::Rc<std::sync::Mutex<i32>>`
cannot be sent between threads safely
   |
   = help: within `[closure@src/main.rs:11:36: 15:10
counter:std::rc::Rc<std::sync::Mutex<i32>>]`, the trait `std::marker::Send` is
not implemented for `std::rc::Rc<std::sync::Mutex<i32>>`
   = note: required because it appears within the type
`[closure@src/main.rs:11:36: 15:10
counter:std::rc::Rc<std::sync::Mutex<i32>>]`
   = note: required by `std::thread::spawn`

編譯錯誤信息中有關鍵的一句:
std::rc::Rc<std::sync::Mutex<i32>> cannot be sent between threads safely。

不幸的是,Rc 並不能安全的在線程間共享。當 Rc 管理引用計數時,它必須在每一個 clone 調用時增加計數,並在每一個克隆被丟棄時減少計數。Rc 並沒有使用任何併發原語,來確保改變計數的操作不會被其他線程打斷。在計數出錯時可能會導致詭異的 bug,比如可能會造成內存泄漏,或在使用結束之前就丟棄一個值。我們所需要的是一個完全類似 Rc,又以一種線程安全的方式改變引用計數的類型。所幸 Arc 正是 這麼一個類似 Rc 並可以安全的用於併發環境的類型。字母 “a” 代表 原子性(atomic),所以這是一個原子引用計數(atomically reference counted)類型。

於是改寫了第三版:

use std::sync::{Mutex, Arc};
use std::thread;
fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];
    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }
    for handle in handles {
        handle.join().unwrap();
    }
    println!("Result: {}", *counter.lock().unwrap());
}

這次編譯通過,並且打印出了正確的結果,最終,在嚴厲的編譯器的逐步引導,“諄諄教誨”下,我們總算寫出了正確的代碼。

Rust編譯器對多線程數據共享,多線程數據傳遞這種內存安全事故多發區進行了極其嚴苛的檢查和限制,確保編譯時就能發現潛在的內存安全問題。在多線程傳遞數據時,除了通過channel,你沒有第二種選擇;在多線程數據共享時,除了Arc+Mutex(如果多線程共享的只是int bool這類簡單數據類型,你還可以使用原子操作) ,你同樣沒有別的選擇。雖然 Rust極其缺乏靈活性,但是這同樣是它的有點,因爲編譯器一直在逼着你寫出正確的代碼,極大減少了程序的維護成本。

以上是我對Rust內存安全保障手段的一些理解,Rust使用一些乍一看很奇怪的特性,非常清晰的定義了一個安全的邊界,並在上面做以足夠的檢查,保證你的代碼不會出問題。Rust做到了沒有垃圾回收的內存安全,沒有數據競爭的併發安全。同時一個新手Rust程序員剛入坑Rust時,大部分的時間都是在解決編譯問題。一個新手C++程序員初期可能會寫出很多不安全的代碼,埋下很多坑,但是新手Rust不會,因爲一個新手Rust寫出的不安全代碼在編譯階段就被攔截了,根本沒有機會埋坑,Rust承諾編譯通過的Rust程序不會存在內存安全問題(注意:如果通過unsafe關鍵字強制關閉安全檢查,則依然有可能出現內存安全問題)。

三 Rust開發效率問題

關於Rust開發效率問題,沒有一個統一的客觀評價標準,基本靠個人主觀感覺而定。每個人對不同語言掌握的熟練度也是影響開發效率的重要因素。關於開發效率,談一談個人的感受:先說入門,由於Rust一些奇葩的語法的存在(最麻煩的莫過於生命週期標記),導致Rust入門不像Python和Golang等語言那樣輕鬆,但是因爲Rust主要是爲了替代C/C++這類系統語言而存在,其借鑑了大量C++的語法,如果對C++熟悉,Rust入門不是難事;其次說說開發速度,對於初學者,Rust開發體驗就像在上海開始實行的垃圾分類時上海人民的那種困惑和凌亂,編譯器檢查太嚴格了,大多數時間都是在解決編譯問題,一種在其它語言中理所當然的寫法,在Rust中就是不行,不過好在編譯器的提示非常友好,根據編譯錯誤提示大多數時候能夠找到答案,不過編譯雖然費事,可一旦編譯通過,程序員就不需要關心內存安全,內存泄漏等頭疼問題,只需要關注於業務邏輯,寫了一個多月的Rust,debug次數屈指可數,而且每次debug都是因爲業務邏輯,從來沒有因爲代碼內存錯誤,崩潰等問題debug;如果對Rust稍微熟練一些,其開發速度絕對不會比Python和Golang慢,因爲在編譯階段,Rust就解決了大部分的問題,省去了大量的debug時間。

四 跨平臺性

Rust跨平臺性和Golang一樣,擁有優秀的跨平臺性,支持交叉編譯,一份代碼可編譯出支持windows、 linux、arm、macos、freebsd等平臺上運行的二進制,且完全靜態編譯,運行時不依賴任何第三方庫。這個特性對於飽受C++跨平臺編譯折磨的程序員來說簡直是福音。Rust對嵌入式環境同樣支持友好,有人用Rust寫了一個簡單的操作系統[6]。

五 生態問題

這一方面應該是Rust最弱的地方,作爲一個後起之秀,其生態遠遠不如Python和Golang豐富,不過使用率很高的一些常用庫都能找到;並且Rust連續3年成爲Stack Overflow最受歡迎的語言[7],受到的關注度越來越高[8],相信未來Rust的社區一定會越來越豐富。

最後靈魂一問收尾:

沒有垃圾回收的內存安全,沒有數據競爭的併發安全、資源消耗低而性能強勁、開發效率高並且跨平臺性優良,這樣的Rust香不香?要不要擁抱一個?

【雲棲號在線課堂】每天都有產品技術專家分享!
課程地址:https://yqh.aliyun.com/live

立即加入社羣,與專家面對面,及時瞭解課程最新動態!
【雲棲號在線課堂 社羣】https://c.tb.cn/F3.Z8gvnK

原文發佈時間:2020-07-21
本文作者:長驅
本文來自:“阿里技術公衆號”,瞭解相關信息可以關注“阿里技術

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章