基本型別 (Primitive Types)
Move 是一種靜態型別語言:每個值都有一個型別,且在編譯時期即可得知。本節將介紹其中最簡單的型別 —— 內建的_基本_型別:布林值與無號整數。它們與下一節介紹的 地址 一起,構成了其他所有型別的基礎材料。
本章的程式碼範例都是節錄片段:像下面這樣的運算式,實際上是放在模組內的一個函式中 —— 通常是 測試函式 —— 為求簡潔我們省略了模組的部分。若想親自試試某個範例,可以把它放進 Hello World 章節所建立套件中的 #[test] 函式裡,然後執行 sui move test。
變數與賦值 (Variables and Assignment)
變數以 let 關鍵字宣告,且預設是_不可變的_:一旦賦值後就無法替換。若需要改變的變數,則要以 let mut 宣告,如此一來才能用 = 運算子重新賦值:
// The type annotation is optional when it can be inferred.
let x: bool = true;
let y = 10u8;
// A `mut` variable can be reassigned with the `=` operator.
let mut z: u8 = 42;
z = 43;
型別標註 —— 也就是名稱後面的 : u8 —— 在編譯器能從值或後續使用推斷出型別時是可選的;寫出它只是為了清楚易讀,並非必要。
變數名稱也可以透過再次宣告來重複使用,這稱為_遮蔽 (shadowing)_。與重新賦值不同,遮蔽會建立一個新變數,因此它對不可變變數也適用,並且可以改變型別:
let x: u8 = 42;
// The new `x` replaces the previous one, and may
// even have a different type.
let x: u16 = (x as u16) + 1;
布林值 (Booleans)
bool 型別只有兩個值 —— 關鍵字 true 和 false —— 且編譯器總是能推斷出來,因此 bool 永遠不需要型別標註。布林值可以與邏輯運算子 &&(且)、||(或)、!(非)組合使用,其中 && 和 || 具有短路求值特性:若左側已經能決定結果,則不會求值右側。
// The type of a boolean is always inferred.
let is_ready = true;
let is_done = false;
// Logical operators: `&&` (and), `||` (or), and `!` (not).
let in_progress = is_ready && !is_done;
布林值用於儲存旗標與驅動條件判斷 —— 也就是 控制流程 章節所介紹的 if 與 while 運算式。
整數型別 (Integer Types)
Move 有六種整數型別,差異僅在於大小 —— 且全部都是_無號的_:Move 中沒有負整數,也沒有專門的有號型別。
| 型別 | 大小(位元) | 最大值 |
|---|---|---|
| u8 | 8 | 255 |
| u16 | 16 | 65_535 |
| u32 | 32 | 4_294_967_295 |
| u64 | 64 | 18_446_744_073_709_551_615 |
| u128 | 128 | 2128 − 1 |
| u256 | 256 | 2256 − 1 |
最常用的是 u64 —— 代幣數量、大小與索引都使用它。整數字面值可以用十進位(42)書寫,並可選擇性地加上底線以提高可讀性(1_000_000),或是用 0x 前綴以十六進位表示(0x2A):
let small: u8 = 42;
let medium: u16 = 1_000; // underscores improve readability
let large: u256 = 100_000_000_000;
let hex: u64 = 0x2A; // hexadecimal literal, 42
雖然 true 和 false 明確地就是布林值,但像 42 這樣的字面值可能是六種整數型別中的任何一種。編譯器會根據值的使用方式推斷型別,預設為 u64;當推斷不夠用時 —— 或是明確寫出來更清楚時 —— 可以用型別標註或字面值後綴來指定型別:
// Both are equivalent.
let x: u8 = 42;
let x = 42u8;
運算 (Operations)
Move 支援整數的標準算術運算:加法、減法、乘法、除法與取模(餘數)。這些運算都不能產生超出該型別範圍的值 —— 運算並不會回繞,而是會直接中止:
| 語法 | 運算 | 中止條件 |
|---|---|---|
| + | 加法 | 結果對該整數型別而言過大 |
| - | 減法 | 結果小於零 |
| * | 乘法 | 結果對該整數型別而言過大 |
| % | 取模(餘數) | 除數為 0 |
| / | 無條件捨去除法 | 除數為 0 |
除法是_無條件捨去_的:沒有小數值,任何餘數都會被捨棄,所以 7 / 2 等於 3。整數也可以用 ==、!=、<、>、<= 和 >= 進行比較,產生一個 bool:
let a = 10u8;
let b = 20u8;
// Comparison produces a `bool`; the operands must be of the same type.
let is_less = a < b; // true
let is_equal = a == b; // false
在每一個運算與比較中,運算元的型別_必須相符_ —— 整數型別之間沒有隱式轉換,將 u8 與 u64 相加會導致編譯錯誤。若要對不同型別進行運算,其中一個運算元必須先明確轉型。
想瞭解更多運算,包含位元運算,請參閱 Move 參考文件。
使用 as 轉型 (Casting with as)
as 運算子可以將整數從一種型別轉換為另一種型別。請注意,含有轉型的運算式通常需要加上括號以避免歧義:
let x: u8 = 42;
let y: u16 = x as u16;
let z = 2 * (x as u16); // ambiguity requires parentheses
_向上_轉型到較大的型別永遠會成功。_向下_轉型則必須能夠容納:與那些會靜默截斷值的語言不同,當數值超出範圍時 Move 會中止:
let x: u16 = 300;
let y = x as u8; // ABORTS! 300 does not fit into `u8`
向上轉型的一個常見用途,是為無法放入原本型別的中間結果騰出空間:
// The same values that would overflow `u8` arithmetic
// fit comfortably once upcast to `u16`.
let x: u8 = 255;
let y: u8 = 255;
let z: u16 = (x as u16) + ((y as u16) * 2);
溢位與下溢 (Overflow and Underflow)
如運算表所示,Move 中的算術運算永遠不會回繞。若運算結果無法放入該型別 —— 過大或小於零 —— 則會在執行時中止:
let x = 255u8;
let y = 1u8;
let z = x + y; // ABORTS! The result does not fit into `u8`
這是一項刻意設計的安全機制。靜默溢位是智能合約 bug 的經典來源 —— 一個餘額回繞成零,或是一個檢查因為某個值悄悄變小而通過。 Move 把每一種這樣的情況都變成一次會讓交易回復的響亮失敗。