Rust 編程之道 - Rust 變量與綁定

Rust 從函數式語言中借鑑了 let 關鍵字建立變量。 Let 建立的變量通常稱爲綁定(Binding),它代表了標識符(Identifier)和值(value)之間創建的一種關聯關係。數組

Rust 位置表達式和值表達式

Rust 中的表達式通常能夠分爲位置表達式(Place Expression)和值表達式(Value Expression)。安全

位置表達式

位置表達式即表示內存位置的表達式。分爲markdown

  • 本地變量
  • 靜態變量
  • 解引用(*expr)
  • 數組索引(expr[expr])
  • 字段引用(expr.field)
  • 位置表達式組合

經過位置表達式能夠對某個數據單元的內存進行讀寫。主要是進行寫操做,也就是位置表達式能夠被賦值的緣由。函數

值表達式

除了位置表達式之外的表達式就是值表達式。 值表達式通常值引用了某個存儲單元地址中的數據。至關於數據值,只能進行讀操做。spa

從語義角度講, 位置表達式表明了數據持久性數據,值表達式表明了臨時數據。位置表達式通常有持久狀態,值表達式通常爲字面量或爲表達式求值過鄭重建立的臨時值。指針

表達式的求值過程在不一樣的上下文中會有不一樣的結果。求值上下文也分爲位置上下文(Place Context)和值上下文(Value Context)code

位置上下文

  • 賦值或者複合賦值語句左側的操做數 a = b + c 其中 a 就是位置上下文
  • 一元表達式的獨立操做數。
  • 包含隱式借用(引用)的操做數。
  • match 判別式或 let 綁定右側在使用 ref 模式匹配的時候也是位置上下文。

除此之外都是值上下文。值表達式不能出如今位置上下文中。示例以下:orm

pub fn temp() -> i32 {
	return 1;
} 

fn main () {
	let x = &temp(); // temp 函數調用是一個無效的位置表達式
	temp() = *x; // error 報錯
}
複製代碼

函數 temp 的調用放到了賦值語句左邊的位置上下文中,此時編譯器報錯,由於 temp 函數調用的是一個無效的位置表達式,它是值表達式。索引

不可綁定與可變綁定

使用 let 關鍵字聲明的位置表達式默認不可變,爲不可變綁定,簡單來說,就 是 let 聲明的變量不可變,相似於 JavaScript 中的 const 關鍵字。ip

fn main () {
	let a = 1; // a 不可變
  // a = 2; immutable and error
	let mut b = 2; // 增長可變聲明
	b = 3;
}
複製代碼

經過 mut 關鍵字,能夠聲明可變的位置表達式,便可變綁定,可變綁定能夠正常修改和賦值。

從語義上來說,let 默認聲明的不可變綁定只能對相應的存儲單元進行讀取,而 let mut 聲明的可變綁定則是能夠對相應的存儲單元進行寫入的。

全部權與引用

當位置表達式出如今值上下文中時,該表達式會把內存地址轉移給另一個位置表達式,這實際上是全部權轉移。

fn main () {
	let place1 = "hello";
	let place2 = "hello".to_string();
  let other = place1;
  println!("{:?}", other);
	let other = place2;
  println("{:?}", other) // error 
}
複製代碼

上面代碼中使用 let 聲明瞭兩個綁定, place1 和 place2。而後將 place1 賦值給新的變量 other。 由於 place1 是一個位置表達式,出如今了賦值操做符的右側,即一個值上下文內,因此 place1 會將內存地址轉移給 other。同理 將 place2 複製給新聲明的 other ,place2 的內存地址一樣會轉移給 other。

第二次聲明 other 將 place2 地址賦值給 other 時,會出現報錯,意爲該處使用了已經移動的值,之因此會出現這種區別,實際上是和底層內存安全管理有關係。這兩種行爲雖然有差異,都是 Rust 爲了安全刻意爲之。

在語義上,每一個變量綁定實際上都擁有該存儲單元的全部權,這種轉移內存地址的行爲就是全部權(OwnerShip) 的轉移,在 Rust 中移動(Move)語義,那種不轉移的狀況其實是一種複製(Copy)語義。Rust 無 GC,因此徹底靠全部權來進行內存管理。

在開發中,通常不須要轉移全部權。Rust 提供了引用操做符(&),能夠直接獲取表達式的存儲單元地址,即內存爲止。能夠經過該內存位置對內存進行讀取。

fn main () {
	let a = [1,2,3];
  let b = &a;     // 獲取內存地址,至關於指針
  println!("{:p}", b);

	let mut c = vec![1,2,3];
  let d = &mut c;
  d.push(4);
  println!("{:?}", d);
}
複製代碼

上面代碼中,b 以 & 的方式獲取 a 的內存地址,這種方式不會引發全部權轉移,由於使用引用操做符已經將賦值表達式右側變成了位置上下文,它知識共享內存位置,經過 println! 宏指定{:p} 格式,能夠打印 b 的指針地址,也就是內存地址。

經過 let mut 聲明動態常數數組c ,經過 &mut 可獲取 c 的可變引用,賦值給 d, 對於 d 的 push 操做,插入新的元素 4。要獲取可變引用,必須先聲明可變綁定。

不管是&a 仍是 &mut c 都至關於對於 a 和 c 全部權的借用,由於 a 和 c 還依舊保留他們的全部權,因此引用也稱之爲借用。

相關文章
相關標籤/搜索