简介: 咱们在抉择一种开发语言时会综合考量各方面的个性,依据理论的需要适当取舍。鱼和熊掌往往不可兼得,要想开发效率高,必然要就义性能和资源耗费,反之亦然。然而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 mutable8 | 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 here5 | let r2 = &mut s; | ^ second mutable borrow occurs here6 | } | - 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 here7 | } | ^ `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 referencedvariables), 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 referencedvariables), 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 here10 | println!("val is {}", val); | ^^^ value used here after move | = note: move occurs because `val` has type `std::string::String`, which doesnot 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) here10 | 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` traiterror[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` traiterror: 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` traiterror[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` traiterror: 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:10counter: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:10counter:std::rc::Rc<std::sync::Mutex<i32>>]`, the trait `std::marker::Send` isnot implemented for `std::rc::Rc<std::sync::Mutex<i32>>` = note: required because it appears within the type`[closure@src/main.rs:11:36: 15:10counter: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