Skip to main content

結構方法 (Struct Methods)

在前面的章節中,我們一直使用點運算子來呼叫值上的函式: v.length()opt.is_some()artist.name()。這就是接收者語法 (receiver syntax) ——「接收者」指的是接收方法呼叫的實例——本節將解釋它的運作方式以及如何控制它。方法讓操作結構的程式碼讀起來更自然:值放在前面,接著是操作,而且不需要匯入或完整拼出函式所屬的模組。

方法語法 (Method Syntax)

核心規則:當一個函式的第一個參數是在同一模組中定義的結構時,該函式就可以用 . 運算子呼叫。這類方法會自動在該結構被使用的任何地方可用——這正是為什麼 vectorOption 的值一出現就能用點語法呼叫的原因。如果第一個參數的型別是在其他模組中定義的,該函式預設不會與結構關聯,必須使用標準的函式呼叫語法——除非如下所示宣告了別名 (alias)

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 模組並新增了另一個型別——VillainHeroVillain 都有類似的欄位名稱和方法。為了避免名稱衝突,我們分別為方法加上 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);
}

在測試函式中,health 方法直接在 HeroVillain 實例上呼叫時不需要前綴,因為編譯器會自動將方法與其對應的結構關聯起來。

注意:在測試函式中,hero.health() 呼叫的是別名方法,而不是直接存取私有的 health 欄位。雖然 HeroVillain 結構是公開的,但它們的欄位在模組內仍是私有的。方法呼叫 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)