乐趣区

关于rust:连续-3-年最受欢迎Rust香

简介: 咱们在抉择一种开发语言时会综合考量各方面的个性,依据理论的需要适当取舍。鱼和熊掌往往不可兼得,要想开发效率高,必然要就义性能和资源耗费,反之亦然。然而 Rust 却出人意料,令人眼前一亮!本文将从性能、内存平安、开发效率、跨平台性及生态等五个方面,对 Rust 这一编程语言进行一些科普性质的分享。

一 性能比照

不同的语言应用不同的内存治理形式,一些语言应用垃圾回收机制在运行时寻找不再被应用的内存并开释,典型的如 Java、Golang。在另一些语言中,程序员必须亲自调配和开释内存,比方 C /C++。Rust 则抉择了第三种形式:内存被一个所有权系统管理,它领有一系列的规定使编译器在编译时进行查看,任何所有权零碎的性能都不会导致运行时开销。Rust 速度惊人且内存利用率极高,规范 Rust 性能与规范 C ++ 性能并驾齐驱,某些场景下效率甚至高于 C ++。因为没有运行时和垃圾回收,它可能胜任对性能要求特地高的服务。网上曾经有了很多对于 Rust 性能剖析比照的文章,不过为了取得一手的材料,还是本人入手来的更加实在。我抉择了 Python,C++,Golang 这 3 种语言来和 Rust 做性能比照。

性能测试场景设计

同样的算法用 4 种语言别离实现,比照在规定的工夫内实现工作的次数。本次测试抉择的算法是找出 10000000 以内的所有素数,比拟在一分钟内实现找出所有素数工作的次数。

源代码链接见 [1]。

动态编译(或者打包)后生成的二进制大小比照

论断:(二进制大小)python > golang > rust > c++

运行速度比照

本场景下比拟 1 分钟内找出 1000000 以内所有素数的次数。

论断:(运行效率)rust > c++ > golang > python

重点来了,在 3 台不同的机器上测试四次的结果显示:Rust 效率竟然高于 C ++!!!

内存耗费比照(粗略计算)

论断:(内存耗费)python > golang > rust > c++

CPU 耗费比照(粗略计算)

论断:(CPU 耗费)golang > python > rust = c++

以上便是我的测试后果,测试代码、二进制和测试后果参考附件 bin.zip,第一次测试后看到后果,有些吃惊,rust 的性能竟然超过了 c ++,不堪设想,于是又在网上搜寻,找到了他人曾经实现的 rust 性能测试,网上的后果更让人吃惊,先看第一篇,原始链接见 [2]。

我间接截图看论断:

以上为 Rust vs Golang。

以上为 Rust vs C++。

论断:以上截图显示,Rust 在性能和资源耗费上不仅大幅度优于 Golang,并且和 C ++ 性能并驾齐驱,某些场景下效率甚至优于 C ++。

以上两种测试场景只是测试一些简略的算法,接下来咱们看一下在理论应用中的性能资源占用比照,仍然是在网上找到了一篇测试报告 [3],该测试报告用 Python、PyPy、Go、Rust 四种语言实现了一个 web 后端,接下来应用 wrk 别离对四个 http 服务器进行压测,该测试场景比拟贴近理论,间接截图看论断:

论断(性能):在理论作为后端服务应用的场景下,Rust 比 Golang 仍然有显著性能劣势。

论断(资源占用):在内存占用上 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 香不香?要不要拥抱一个?

相干链接

[1]https://github.com/famzah/langs-performance
[2]https://benchmarksgameteam.pages.debian.net/benchmarksgame/fastest/rust-gpp.html
[3]https://deavid.wordpress.com/2019/10/12/benchmarking-python-vs-pypy-vs-go-vs-rust/
[4]https://www.chromium.org/Home/chromium-security/memory-safetyhttps://www.zdnet.com/article/microsoft-70-percent-of-all-security-bugs-are-memory-safety-issues/
[5]https://www.bookstack.cn/read/trpl-zh-cn/src-ch10-03-lifetime-syntax.md
[6]https://github.com/redox-os/redox
[7]https://stackoverflow.blog/2020/06/05/why-the-developers-who-use-rust-love-it-so-much/
[8]https://blog.discord.com/why-discord-is-switching-from-go-to-rust-a190bbca2b1f

退出移动版