結構方法 (Struct Methods)
在前面的章節中,我們使用點運算子在值上呼叫函式: v.length()、opt.is_some()、artist.name()。這是接收者語法(receiver syntax)——「接收者」是指接收方法呼叫的執行個體——本節將說明其運作方式以及如何控制它。方法能讓操作結構的程式碼讀起來更自然:值在前,操作在後,而且無須匯入或明確寫出函式所屬的模組。
方法語法 (Method Syntax)
核心規則是:當函式的第一個引數是與函式定義於相同模組中的結構時,即可使用 . 運算子呼叫該函式。這類方法會自動在使用該結構的任何地方可用——這正是為什麼取得 vector 和 Option 值後,便能立即以點語法呼叫它們。如果第一個引數的型別定義於另一個模組,該函式預設不會與該結建置立關聯,必須使用標準函式呼叫語法——除非如以下所示宣告了別名。
module book::hero;
/// A struct representing a hero.
public struct Hero has drop {
health: u8,
mana: u8,
}
/// Create a new Hero.
public fun new(): Hero { Hero { health: 100, mana: 100 } }
/// A method which casts a spell, consuming mana.
public fun heal_spell(hero: &mut Hero) {
hero.health = hero.health + 10;
hero.mana = hero.mana - 10;
}
/// A method which returns the health of the hero.
public fun health(hero: &Hero): u8 { hero.health }
/// A method which returns the mana of the hero.
public fun mana(hero: &Hero): u8 { hero.mana }
#[test_only]
use std::unit_test::assert_eq;
#[test]
// Test the methods of the `Hero` struct.
fun test_methods() {
let mut hero = new();
hero.heal_spell();
assert_eq!(hero.health(), 110);
assert_eq!(hero.mana(), 90);
}
方法別名 (Method Aliases)
當模組定義多個結構及其方法時,方法別名有助於避免名稱衝突。它們也能為結構提供描述性更強的方法名稱。
語法如下:
// 用於區域方法關聯
use fun function_path as Type.method_name;
// 匯出的別名
public use fun function_path as Type.method_name;
僅允許為定義於相同模組中的結建置立公開別名。對於定義於其他模組中的結構,仍可建立別名,但不可將其設為公開。
在以下範例中,我們修改了 hero 模組並新增另一個型別——Villain。Hero 和 Villain 都有相似的欄位名稱與方法。為避免名稱衝突,我們分別以 hero_ 和 villain_ 作為方法前綴。不過,使用別名後,便可在結構執行個體上呼叫這些方法,而無須加上前綴:
module book::hero_and_villain;
/// A struct representing a hero.
public struct Hero has drop {
health: u8,
}
/// A struct representing a villain.
public struct Villain has drop {
health: u8,
}
/// Create a new Hero.
public fun new_hero(): Hero { Hero { health: 100 } }
/// Create a new Villain.
public fun new_villain(): Villain { Villain { health: 200 } }
// Alias for the `hero_health` method. It will be imported automatically when
// the module is imported.
public use fun hero_health as Hero.health;
public fun hero_health(hero: &Hero): u8 { hero.health }
// Alias for the `villain_health` method. Will be imported automatically
// when the module is imported.
public use fun villain_health as Villain.health;
public fun villain_health(villain: &Villain): u8 { villain.health }
#[test_only]
use std::unit_test::assert_eq;
#[test]
// Test the methods of the `Hero` and `Villain` structs.
fun test_associated_methods() {
let hero = new_hero();
assert_eq!(hero.health(), 100);
let villain = new_villain();
assert_eq!(villain.health(), 200);
}
在測試函式中,會直接在 Hero 與 Villain 執行個體上呼叫 health 方法,無須加上前綴,因為編譯器會自動將方法與各自的結建置立關聯。
注意:在測試函式中,hero.health() 呼叫的是別名方法,而不是直接存取私有的 health 欄位。雖然 Hero 與 Villain 結構是公開的,但其欄位仍僅限模組內存取。方法呼叫 hero.health() 使用了由 public use fun hero_health as Hero.health 定義的公開別名,以受控方式存取私有欄位。
外部型別方法的別名 (Aliasing a Method of an External Type)
別名不限於模組本身的結構:區域(非公開)別名可以將方法名稱附加至其他模組的型別。在此,我們為標準 String 型別提供額外的方法名稱 num_bytes——這比其 length 函式實際計算的內容更精確:
module book::string_alias;
use std::string::String;
/// Alias `std::string::length` as `String.num_bytes`.
/// A local alias can be declared for any type, even an external one.
use fun std::string::length as String.num_bytes;
#[test_only]
use std::unit_test::assert_eq;
#[test]
fun test_string_alias() {
let s: String = "Hello";
// Same function, two names: the built-in method and our alias.
assert_eq!(s.length(), 5);
assert_eq!(s.num_bytes(), 5);
}
別名僅存在於宣告它的模組中——這正是它不可為 public 的原因:該模組並不擁有 String 型別,因此無法為所有其他人擴充其介面。
延伸閱讀 (Further Reading)
- Move 參考文件中的方法語法。