我對Rust也是初學者的狀態,以下是從A 30 minute introduction to Rust看來的,既然Rust被定位成System programming language,難免要拿來跟C++比一比。看看Ruat友什麼特別之處。
The power of ownership
在C/C++,很簡單可以寫出這樣的程式碼。
1 | int* dangling(void) |
i
是存在Stack上,隨時都有被覆蓋過去的危險,不過現在主流的Compiler都會產生Warning,告訴你這段Code可能會有問題。
Rust版
1 | fn dangling() -> &int { |
這段Code根本編譯不過,編譯器會告訴你i
的lifecycle只存活於dangling中,不能轉移給外部使用。
如果將i分配到Heap的話,大概會是這樣
1 | fn dangling() -> Box<int> { |
i
的控制權由dnagling轉移到add_one,等到沒有用的時候,就會被釋放掉。相當於C++11的unique_ptr>
1 | unique_ptr<int> dangling(void) |
最大的差異在於,Box不能為null。雖然用途被侷限了,不過可以減少很多錯誤。
C++有太多方式可以存取記憶體了,而Rust只有一種,所以要寫出正確的C++ Code會比Rust難。
Owning concurrency
在Rust中,物件的控制權也可以在Task中轉移。
1 | fn main() { |
C++11可以作到同樣的事情
1 | void func(vector<int> v) |
最大的差異,將上面註解拿掉,可以看到Rust可以在Compile-time偵測出錯誤,而C++需要在Runtime才會知道。
如果要在多的Task中使用同一份資料時,可以採取以下兩種方法
- 每個Task都拿到一份副本當Task很多或是資料很大時,這個方案就不適用了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14fn main() {
let numbers = vec![1i, 2i, 3i];
for num in range(0u, 3) {
let (tx, rx) = channel();
// Use `clone` to send a *copy* of the array
tx.send(numbers.clone());
spawn(proc() {
let numbers = rx.recv();
println!("{:d}", *numbers.get(num as uint));
})
}
} - Atomically reference counted (ARC)
Rust推薦的方式相當於C++11 shared_ptr的應用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17use std::sync::Arc;
fn main() {
let numbers = vec![1i, 2i, 3i];
let numbers = Arc::new(numbers);
for num in range(0u, 3) {
let (tx, rx) = channel();
tx.send(numbers.clone());
spawn(proc() {
let numbers = rx.recv();
// *numbers.get_mut(num as uint) = *numbers.get_mut(num as uint) + 1;
println!("{:d}", *numbers.get(num as uint));
})
}
}最大的差異在於,Rust的Arc是1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21#include <iostream>
#include <thread>
#include <vector>
#include <memory>
using namespace std;
void func(shared_ptr<vector<int>> &v, int i)
{
cout << (*v)[i] << endl;
}
int main() {
shared_ptr<vector<int>> number(new vector < int > { 1, 2, 3 });
thread t[3];
for (int i = 0; i < 3; i++)
{
thread t1 = thread(func, std::ref(number), i);
t[i] = std::move(t1);
}
for (int i = 0; i < 3; i++)
t[i].join();
return 0;
}immutable
※,而C++是mutable
。
就算寫成這樣,也無法改變資料被改變的事實。除此之外,也有可能出現Race condtion。immutable代表著不需要加Lock也能放新的在Thread中共享數據。這邊的const,只能保護v這個值不被更改,對於v所指的內容無法保護。1
2
3
4
5void func(const shared_ptr<vector<int>> &v, int i)
{
cout << (*v)[i] << endl;
(*v)[i] = (*v)[i] + 1;
}
如果要在多個Task中修改的話,就只能這麼做這邊就等同於上面的例子加上個Mutex保護,這邊就不多寫了。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27use std::sync::{Arc, Mutex};
fn main() {
let numbers = vec![1i, 2i, 3i];
let numbers_lock = Arc::new(Mutex::new(numbers));
for num in range(0u, 3) {
let (tx, rx) = channel();
tx.send(numbers_lock.clone());
spawn(proc() {
let numbers_lock = rx.recv();
// Take the lock, along with exclusive access to the underlying array
let mut numbers = numbers_lock.lock();
// This is ugly for now, but will be replaced by
// `numbers[num as uint] += 1` in the near future.
// See: https://github.com/rust-lang/rust/issues/6515
*numbers.get_mut(num as uint) = *numbers.get_mut(num as uint) + 1;
println!("{}", *numbers.get(num as uint));
// When `numbers` goes out of scope the lock is dropped
})
}
}
跟現在的C++11比較起來,Owenership這邊可以靠unique_ptr來處理,不過根本性的差異在於Rust認為資料預設為immutable的,所以很多問題可以簡化,透過編譯氣在Compile-time可以偵測出很多問題,而C++只能靠Programmer花時間檢測。