變數綁定
事實上所有不是「Hello World」的 Rust 程式都會用到 變數綁定。
他們將數值綁定到一個名字上,以便在之後使用它。
let
被用來聲明一個綁定,就像:
fn main() {
let x = 5;
}
在所有範例中寫上 fn main() {
有點冗餘,所以我們在後面都會省略它。
如果你一路看下去,請記得要寫上 main()
函式,不要忘記了。
否則你會得到錯誤訊息。
模式(Patterns)
在很多語言,變數綁定被叫做 變數(variable),但是 Rust 的變數綁定藏有秘招。
例如 let
表達式左邊是一個 模式,而不是變數名稱。
這代表我們可以做一些如下的事:
let (x, y) = (1, 2);
在這個表達式計算後,x
會是 1,而 y
會是 2。
模式非常強大,而且在本書中有著 單獨的章節。
現在我們還不需要這些功能,所以我們只要記住有這個東西,然後繼續就可以了。
型別註釋(Type annotations)
Rust 是靜態型別語言,這代表我們要先具體指定我們所需的型別,然後它們會在編譯期被檢查。 那為什麼第一個例子可以編譯過呢? 恩,因為 Rust 有一個「型別推斷」(type inference)的功能。 如果它能判斷某個東西的型別,你就不需要確切地指明出來。
但如果我們想要,也可以加上型別。
型別寫在冒號(:
)後面:
let x: i32 = 5;
如果我要求你大聲唸給班上同學聽,你應該唸成「x
被綁定為 i32
型別,而且數值是 5
。」
這個例子中我們宣稱 x
為一個 32 位元的帶號整數(signed integer)。
Rust 有許多不同的基本整數型別。
以 i
開頭的是帶號整數(signed integers),而以 u
開頭的是非帶號整數(unsigned integers)。
整數可能的大小有 8、16、32、及 64 位元。
之後的範例,我們會把型別註釋在註解中。 範例會像這樣:
fn main() {
let x = 5; // x: i32
}
注意,註釋和 let
語法很類似。
Rust 習慣上不會寫上這些註解,但我們偶爾會加上它來幫助你理解 Rust 推斷出的是什麼型別。
可變性(Mutability)
綁定預設是 不可變的(immutable)。 下面的原始碼無法編譯:
let x = 5;
x = 10;
他會給你以下錯誤訊息:
error: re-assignment of immutable variable `x`
x = 10;
^~~~~~~
如果你要將綁定成為可變的(mutable),你可以用 mut
:
let mut x = 5; // mut x: i32
x = 10;
有許多理由使得綁定預設是不可變的,但我們可以藉由一個 Rust 的一個主要目標來想想看為什麼:那就是安全。
如果你忘記宣告 mut
,編譯器會抓到它,然後讓你知道你也許改了某些並非真的想要改的東西。
如果綁定預設是可變的,編譯器就無法告訴你了。
如果你 真的 打算要改變,解決方式很簡單:加上 mut
。
盡可能的避免改變狀態有其他許多的好理由,但是它們並不在本指南的範圍內。 一般來說,你會避免大辣辣的直接使用可變數(mutation),這也是 Rust 所希望的。 雖然如此,有時候你還是會需要可變數,所以它並沒有被禁止使用。
初始化綁定(Initializing bindings)
Rust 的變數綁定有跟其他語言不同的一個部分:綁定要在使用前初始化一個值。
讓我們試試看。
把你的 src/main.rs
改成以下這樣:
fn main() {
let x: i32;
println!("Hello world!");
}
你可以輸入 cargo build
去建構它。
你將會得到警告訊息,但它仍會印出「Hello, world!」:
Compiling hello_world v0.0.1 (file:///home/you/projects/hello_world)
src/main.rs:2:9: 2:10 warning: unused variable: `x`, #[warn(unused_variable)]
on by default
src/main.rs:2 let x: i32;
^
Rust 警告我們沒有使用這個變數綁定,但的確我們沒有使用它,所以沒關係。
只是如果我們真的使用 x
,事情就不一樣了。
讓我們試試看。
把你的程式改成這樣:
fn main() {
let x: i32;
println!("The value of x is: {}", x);
}
然後試著建構它。 你會得到錯誤訊息:
$ cargo build
Compiling hello_world v0.0.1 (file:///home/you/projects/hello_world)
src/main.rs:4:39: 4:40 error: use of possibly uninitialized variable: `x`
src/main.rs:4 println!("The value of x is: {}", x);
^
note: in expansion of format_args!
<std macros>:2:23: 2:77 note: expansion site
<std macros>:1:1: 3:2 note: in expansion of println!
src/main.rs:4:5: 4:42 note: expansion site
error: aborting due to previous error
Could not compile `hello_world`.
Rust 不讓我們使用沒有被初始化的值。
接著,讓我們討論我們加在 println!
的東西。
如果你在要印出的字串中加入大括號,Rust 會理解為這是一個插入數值的要求。
字串插值(string interpolation)是個電腦科學術語,代表「插入到字串中」。
我們加上一個逗號和 x
,來表示我們想要用 x
當成插入的值。
逗號是當我們傳遞多個參數時,用來分隔我們傳遞給函式和巨集的參數用的。
當你使用大括號時,Rust 會試著檢查型別,用有意義的方式顯示數值。 如果你想用更詳細的方式去指定格式,這邊有 很多方式可供選擇。 現在,我們使用預設方式插入:因為印出整數並不難。
有效範圍及遮蔽(Scope and shadowing)
讓我們回到綁定。
變數綁定有有效範圍 - 它們被限制存活在它們被定義的區塊中。
一個區塊(block)是一個被用 {
和 }
包起來的陳述式的集合。
函式的定義也是一個區塊!
在以下範例我們定義兩個變數綁定,x
和 y
,他們存在於不同區塊中。
x
可以在 fn main() {}
內存取,而 y
只能在內部區塊(inner block)存取。
fn main() {
let x: i32 = 17;
{
let y: i32 = 3;
println!("The value of x is {} and value of y is {}", x, y);
}
println!("The value of x is {} and value of y is {}", x, y); // This won't work
}
第一個 println!
會印出「The value of x is 17 and the value of y is 3」,但是本範例無法成功編譯,因為第二個 println!
不能存取 y
值,因為它已經不在有效範圍內了。
我們會得到以下錯誤訊息:
$ cargo build
Compiling hello v0.1.0 (file:///home/you/projects/hello_world)
main.rs:7:62: 7:63 error: unresolved name `y`. Did you mean `x`? [E0425]
main.rs:7 println!("The value of x is {} and value of y is {}", x, y); // This won't work
^
note: in expansion of format_args!
<std macros>:2:25: 2:56 note: expansion site
<std macros>:1:1: 2:62 note: in expansion of print!
<std macros>:3:1: 3:54 note: expansion site
<std macros>:1:1: 3:58 note: in expansion of println!
main.rs:7:5: 7:65 note: expansion site
main.rs:7:62: 7:63 help: run `rustc --explain E0425` to see a detailed explanation
error: aborting due to previous error
Could not compile `hello`.
To learn more, run the command again with --verbose.
另外,變數綁定可以被遮蔽(shadowed)。 這代表與其他變數綁定有同樣名稱的後一個變數綁定,在有效範圍內,將會覆蓋前一個綁定。
let x: i32 = 8;
{
println!("{}", x); // Prints "8"
let x = 12;
println!("{}", x); // Prints "12"
}
println!("{}", x); // Prints "8"
let x = 42;
println!("{}", x); // Prints "42"
遮蔽(shadowing)和可變綁定(mutable bindings)也許看似像一枚硬幣的兩面,但其實它們是兩種不同的概念,所以並不一定總是可以直接替換。 作為其中之一,遮蔽可以讓我們重新綁定名稱到不同型別的變數。 它也可以改變綁定的可變性。
let mut x: i32 = 1;
x = 7;
let x = x; // x is now immutable and is bound to 7
let y = 4;
let y = "I can also be bound to text!"; // y is now of a different type
commit 6ba9520