guest@blog.cmj.tw: ~/posts $

Rust 101


another trend for system-design programming

記錄一下學習 Rust 中的一些技巧:

ownership

在 Rust 中擁有權 (ownership) 是一個跟其他程式不太一樣的概念。不像其他語言在 Rust 中廣泛的區別 Stack 跟 Heap 的使用時機,因為在系統設計 (system design) 中這兩者的效率差異很大。在擁有權下 Rust 訂定的以下三個規則:

  • 所有值都有一個變數是他的擁有者
  • 一次只有一個擁有者
  • 當值離開擁有者的範圍時即刪除

在使用引用 (reference) 時有下面兩個規則:

  • 任何時候可以擁有一個可變動的引用 (mutable reference) 或任意數量不可動的引用 (immutable reference)
  • 引用必須合法

move / clone / copy

另一個 Rust 不一樣的概念則是變數間的操作。在多數語言中都把值在不同變數間傳遞,在 Rust 的世界稱為 move。在變數間 move 時,如果傳遞的內容為記憶體空間 (絕大多數的 case),則被移動的值就被視為不再使用, 而且 Rust 也不會針對被轉移的變數釋放記憶體 (記憶體已經轉移到新的變數)。

當需要將整體內容轉移到另一個變數而並非只有記憶體位址時,Rust 的世界則是使用 clone 來做深層拷貝 (deep copy)。然而這種操作顯而易見的花費昂貴的記憶體空間與運算時間。而另外一種拷貝則是 copy, 這是僅限於編譯時期 (compile-time) 知道物件大小使用並不能支援 Drop 這個 trait。

在函數呼叫時變數傳遞僅使用 movecopy 來傳遞值,而回傳時則會用 move 回傳變數。在函數結束時, 除非擁有權已經被轉移出去,值會自動被 Drop 刪除。

mutable

另外一個在 Rust 的限制則是 mutable。預設情況下所有的變數在 Rust 中都是唯讀不可改變,當需要改變時, 則需要明確使用 mut

在使用上還需要知道拿到的否可更改的值,下面的程式碼顯示 v 拿到的是一個可變動的參照 (reference), 之後 v 可以修改內部的內容但不能修改 v 本身的值。

let v = &mut[(1, 2), (3, 4)];

println!("{:?}", v);
v[0] = (0, -1);			// modify the data in reference
println!("{:?}", v);

lifetime

在 rust 的世界裡面所有變數都有生命週期 (lifetime),用來指示參考 (reference) 的引用範圍 (scope)。 在大多數的情空下,生命週期是隱含並可推斷的:程式碼中可以不用明確定義其生命週期 (跟變數型態一樣)。 當 Rust 在編譯時間無法判斷唯一可能的生命週期時,需要額外註釋 (annotate) 其生命週期。 常見的狀況就是引用懸空 (dangling references),也就是引用一個可能會被清空的參考。像是下面則是一個例子:

fn dangling_reference() {
	let x;

	{
		let y = 5;
		x = &y;
	}

	println!("{}", x);	// borrow later used here
}

在沒有使用 println!("{}", x) 時程式碼可以正常編譯,但是當加入這行則會跳出 borrow later used here, 代表變數 x 此此時的值是參考 y 的值但已經在此時被消滅。

在資料結構中,可以使用 struct<'a> 宣告生命週期為 a,其中內部的參照就可以使用相關定義

struct Foo<'a> {
	x: &'a i32,
}

super trait

trait 定義一個實作標準,讓物件透過實作 (impl) 之後支援特定的函數。

supertraits 除了定義 trait 之外,還限制 trait 需要滿足另外一個 trait。 語法是 trait TRAIT : TRAIT ( '+' TRAIT )* { ... }