Move 2024 遷移指南 (Move 2024 Migration Guide)
Move 2024 是 Mysten Labs 維護的 Move 語言目前版本,也是本書所教授的版本。本指南是為了將程式碼——或知識——從原始版本(以下稱為 Move 2020)遷移過來的讀者所寫:本指南逐一列出每個功能的變更,並針對每項變更提供修改前後的範例對照。
本指南為高階概覽。這裡列出的每個功能在本書中都有專屬章節,可從標題連結進去——完整內容請參閱各章節。
使用 2024 版本 (Using the 2024 Edition)
版本是在 Package Manifest 的 [package] 區段中指定的。 穩定的 2024 版本是預設選擇,也是建議使用的版本;2024.beta 和 2024.alpha 版本則提供仍在開發中、可能會變動的功能的搶先體驗:
[package]
name = "my_package"
edition = "2024"
遷移工具 (Migration Tool)
Move CLI 有一個遷移工具,可以將舊版程式碼更新到新版本。要使用遷移 工具,請在套件目錄中執行以下指令:
$ sui move migrate
遷移工具會處理機械式的變更:let mut 語法、struct 上的 public 修飾詞,以及用 public(package) 可見性取代 friend 宣告。
模組標籤 (Module Label)
參閱 Module。
模組不再需要將其主體包裝在區塊中:_模組標籤_語法宣告模組一次,之後的所有內容都屬於該模組——為整個檔案節省了一層縮排。區塊語法仍受支援,但僅在同一檔案中宣告多個模組時才有用,而這並非建議的做法:
// Move 2020:模組區塊
module book::my_module {
public struct Book {}
}
// Move 2024:模組標籤
module book::my_module;
public struct Book {}
使用 let mut 進行可變綁定 (Mutable Bindings with let mut)
參見 基本型別。
Move 2024 要求使用 mut 關鍵字來宣告可以被重新賦值或可變借用的變數。編譯器在嘗試修改未宣告 mut 的變數時會發出錯誤:
// Move 2020
let x: u64 = 10;
x = 20;
// Move 2024
let mut x: u64 = 10;
x = 20;
此外,mut 關鍵字也用於元組解構與函式引數中,放置在變數名稱之前:
// 依值傳遞並修改
fun takes_by_value_and_mutates(mut v: Value): Value {
v.field = 10;
v
}
// 用於元組解構
fun destruct() {
let (mut x, y) = point::get_point();
}
// 用於結構體解包
fun unpack() {
let Point { x, mut y } = point::get_point();
}
Struct 可見性 (Struct Visibility)
請參考使用 Struct 的自訂型別。
在 Move 2024 中,struct 宣告需要可見性修飾詞。目前唯一可用的可見性是 public:
// Move 2020
struct Book {}
// Move 2024
public struct Book {}
請注意 public 套用於 struct 的 型別 ——欄位仍維持模組內部可見,且只有定義該 struct 的模組才能封裝與解封裝該 struct,與先前完全相同。
Friends 已棄用 (Friends Are Deprecated)
參見可見性修飾符。
friend 宣告與 public(friend) 可見性已棄用。取而代之,public(package) 可見性讓函式可以被同一 package 內的任何模組呼叫——不需要任何宣告。friend book::module_name; 陳述式已完全移除:
// Move 2020
friend book::friend_module;
public(friend) fun protected_function() {}
// Move 2024:不需要 friend 宣告
public(package) fun protected_function() {}
方法語法 (Method Syntax)
參見 Struct Methods。
第一個參數是同一模組中定義型別的函式會成為該型別的_方法_,可在該型別出現的任何地方以點語法呼叫:
public fun count(c: &Counter): u64 { /* ... */ }
fun use_counter(c: &Counter) {
// Move 2020
let count = counter::count(c);
// Move 2024
let count = c.count();
}
標準函式庫與 Sui Framework 充分運用了這一點:原生型別與標準型別都內建了相關聯的方法:
// vector 轉為 string 與 ascii string
let str: String = b"Hello, World!".to_string();
let ascii: ascii::String = b"Hello, World!".to_ascii_string();
// address 轉為 bytes
let bytes = @0xa11ce.to_bytes();
use fun 與方法別名 (use fun and Method Aliases)
參見 Struct Methods。
use fun 宣告會將一個函式在指定的方法名稱下與某個型別建立關聯。若該型別是模組本地的,可以為它宣告一個別名;若該型別是在同一個模組中定義的,則可以使用 public use fun 公開宣告:
// 本地:該型別對此模組而言是外部型別
use fun my_custom_function as vector.do_magic;
// 匯出:該型別是在同一個模組中定義的
public use fun kiosk_owner_cap_for as KioskOwnerCap.kiosk;
借用的索引語法 (Index Syntax for Borrowing)
參見 Move 參考文件中的 Vector 與 Index Syntax。
方括號取代了集合型別上明確的 borrow 與 borrow_mut 呼叫:
fun play_vec() {
let mut v = vector[1, 2, 3, 4];
let first = &v[0]; // 呼叫 vector::borrow(&v, 0)
let first_mut = &mut v[0]; // 呼叫 vector::borrow_mut(&mut v, 0)
let first_copy = v[0]; // 呼叫 *vector::borrow(&v, 0)
}
此語法由 vector 以及 Sui Framework 的集合型別所支援:VecMap、 Table、Bag、ObjectTable、ObjectBag 與 LinkedTable。自訂型別可以透過將其 borrow 函式標記 #[syntax(index)] 屬性來實作此語法:
#[syntax(index)]
public fun borrow<T>(c: &List<T>, key: String): &T { /* ... */ }
#[syntax(index)]
public fun borrow_mut<T>(c: &mut List<T>, key: String): &mut T { /* ... */ }
字串字面值 (String Literals)
參見 String。
Move 2020 只提供 byte-string 字面值,建構 String 需要明確轉換。新版本新增了字串字面值 "...",其型別會根據上下文推斷——會變成 String、ascii::String,或 vector<u8>,依需求而定:
// Move 2020:位元組,於執行期轉換
let str: String = string::utf8(b"Hello");
// Move 2024:此字面值在編譯期會被檢查並賦予型別
let str: String = "Hello";
let ascii: std::ascii::String = "ASCII";
內容會在編譯時期進行驗證:若字面值用作 ascii::String,則必須只包含 ASCII 字元,否則程式碼將無法編譯。
列舉與 match (Enums and match)
參見 Enums and Match。
Move 2024 引入了_列舉_ —— 具有多個變體的使用者定義型別 —— 以及用來處理它們的 match 運算式。兩者搭配可以用單一型別表達多種不同的資料結構,這是先前需要用多個結構體與執行期檢查來模擬的:
/// 一種型別 —— 三種不同的資料形狀。
public enum Segment has copy, drop {
Empty,
String(String),
Special { content: vector<u8>, encoding: u8 },
}
public fun is_empty(s: &Segment): bool {
match (s) {
Segment::Empty => true,
_ => false,
}
}
match 運算式並不限於列舉:它同樣適用於基本型別與結構體,要求各分支必須窮盡所有情況,並支援用 _ 萬用字元處理剩餘情況。
巨集 (Macros)
請參閱 巨集函式。
Move 2024 引入了_巨集函式_ —— 這是在編譯期於呼叫端展開的函式,可以接受_lambda_作為參數。巨集名稱後面會接 ! 符號:
// 可以透過 `for!(0, 10, |i| call(i));` 呼叫
macro fun for($start: u64, $stop: u64, $body: |u64|) {
let mut i = $start;
let stop = $stop;
while (i < stop) {
$body(i);
i = i + 1
}
}
熟悉的 assert! 不再是編譯器特殊處理的魔法 —— 它現在是一個普通的巨集,而且其錯誤碼參數現在是選填的。標準函式庫提供了一組豐富的巨集,這些巨集很快就成為撰寫迭代的慣用方式:
let v = vector[1, 2, 3];
// 取代手寫的 while 迴圈:
let doubled = v.map!(|n| n * 2);
let sum = v.fold!(0, |acc, n| acc + n);
v.do!(|n| std::debug::print(&n));
不使用中止碼中止 (Abort Without a Code)
參見中止執行。
中止碼現在是可選的:單獨的 abort(以及不帶第二個參數的 assert!)會自動推導出碼,編碼失敗發生的模組與原始碼行號。這很適合用在預期不會被觸及的分支:
// Move 2020: 一定要提供 code
if (!is_valid) abort 0;
// Move 2024
if (!is_valid) abort;
assert!(is_valid);
巧妙的錯誤 (Clever Errors)
參閱 中止執行。
標記 #[error] 屬性的錯誤常數可以帶有人類可讀的訊息 — 使用 vector<u8> 而非單純的 u64。中止時,工具鏈會解碼常數名稱、訊息與原始碼行號,不再需要查找數字代碼:
#[error]
const ENotAuthorized: vector<u8> = "The caller is not authorized to perform this action";
public fun protected_action(/* ... */) {
assert!(is_authorized, ENotAuthorized);
}
在測試中擴充模組 (Extending Modules in Tests)
請參閱 擴充外部模組 (Extending Modules)。
extend module 宣告可以為現有模組——包括來自外部套件的模組——新增僅供測試使用的成員,並完整存取其私有型別。這解決了長久以來測試沒有提供測試工具的依賴項時所遇到的問題:
#[test_only]
extend module pyth::price_info;
// 這裡定義的函式可以在測試中
// 封裝與解封裝 `pyth::price_info` 的私有型別
模組擴充功能仍在開發中,目前需要 2024.alpha edition。
延伸閱讀 (Further Reading)
- Sui Blog 上的 Move 2024 遷移指南。