Skip to main content

函式 (Functions)

函式定義在模組 (Modules) 內部,並定義了該模組的邏輯與行為。函式可以被重複使用,既可以從其他函式中呼叫,也可以作為執行的進入點。

宣告

函式使用 fun 關鍵字進行宣告,後跟函式名稱、類型參數、參數、回傳類型,最後是函式主體。

<可見性>? <entry>? <macro>? fun <識別碼><[類型參數: 約束],*>([識別碼: 類型],*): <回傳類型> <函式主體>

例如:

fun foo<T1, T2>(x: u64, y: T1, z: T2): (T2, T1, u64) { (z, y, x) }

可見性 (Visibility)

預設情況下,模組函式只能在同一個模組內被呼叫。這些內部(有時稱為私有)函式不能從其他模組呼叫,也不能作為執行的進入點。

module a::m {
fun foo(): u64 { 0 }
fun calls_foo(): u64 { foo() } // 有效
}

module b::other {
fun calls_m_foo(): u64 {
a::m::foo() // 錯誤!
// ^^^^^^^^^^^ 'foo' 對 'a::m' 是內部的
}
}

若要允許從其他模組存取,函式必須宣告為 publicpublic(package)。與可見性切線相關的是,entry 函式可以被呼叫作為執行的進入點。

public 可見性

public 函式可以被定義在 任何 模組中的 任何 函式呼叫。如下例所示,public 函式可以被以下來源呼叫:

  • 定義在同一個模組中的其他函式,
  • 定義在另一個模組中的函式,或
  • 作為執行的進入點。
module a::m {
public fun foo(): u64 { 0 }
fun calls_foo(): u64 { foo() } // 有效
}

module b::other {
fun calls_m_foo(): u64 {
a::m::foo() // 有效
}
}

有關執行進入點的更多細節,請參閱下方章節

public(package) 可見性

public(package) 可見性修飾符是 public 修飾符的一種更受限形式,旨在對函式的使用位置提供更多控制。public(package) 函式可以被以下來源呼叫:

  • 定義在同一個模組中的其他函式,或
  • 定義在同一個套件 (Package)(相同的地址)中的其他函式。
module a::m {
public(package) fun foo(): u64 { 0 }
fun calls_foo(): u64 { foo() } // 有效
}

module a::n {
fun calls_m_foo(): u64 {
a::m::foo() // 有效,同樣在 `a` 中
}
}

module b::other {
fun calls_m_foo(): u64 {
a::m::foo() // 錯誤!
// ^^^^^^^^^^^ 'foo' 只能從 `a` 中的模組呼叫
}
}

已棄用:public(friend) 可見性

在引入 public(package) 之前,public(friend) 被用於允許同一個套件中的函式進行有限的公開存取,但必須由被呼叫者的模組顯式列舉允許的模組清單。詳情請參閱 朋友圈 (Friends)

entry 修飾符

除了 public 函式之外,你的模組中可能還有一些函式想要作為執行的進入點。entry 修飾符旨在允許模組函式啟動執行,而無需將功能暴露給其他模組。

從本質上講,publicentry 函式的組合定義了一個模組的「主 (main)」函式,它們指定了 Move 程式可以從何處開始執行。

不過請記住,entry 函式 仍然 可以被其他 Move 函式呼叫。因此,雖然它們 可以 作為 Move 程式的起點,但它們並不侷限於這種情況。

例如:

module a::m {
entry fun foo(): u64 { 0 }
fun calls_foo(): u64 { foo() } // 有效!
}

module a::n {
fun calls_m_foo(): u64 {
a::m::foo() // 錯誤!
// ^^^^^^^^^^^ 'foo' 對 'a::m' 是內部的
}
}

entry 函式的參數和回傳類型可能會受到限制。不過,這些限制具體取決於 Move 的每個個別部署。

有關 Sui 上 entry 函式的文件可以在這裡找到。

為了方便測試,可以從 #[test]#[test_only] 上下文中呼叫 entry 函式。

module a::m {
entry fun foo(): u64 { 0 }
}
module a::m_test {
#[test]
fun my_test(): u64 { a::m::foo() } // 有效!
#[test_only]
fun my_test_helper(): u64 { a::m::foo() } // 有效!
}

macro 修飾符

與普通函式不同,macro (巨集) 函式在執行階段並不存在。相反,這些函式在編譯期間會被內聯 (inline) 替換到每個呼叫點。這些 macro 函式利用這種編譯過程提供超出標準函式的功能,例如接受高階的 lambda 風格函式作為參數。這些 lambda 參數同樣在編譯期間展開,允許你將函式主體的部分內容作為參數傳遞給巨集。例如,考慮以下簡單的循環巨集,其中循環主體是以 lambda 形式提供的:

macro fun n_times($n: u64, $body: |u64| -> ()) {
let n = $n;
let mut i = 0;
while (i < n) {
$body(i);
i = i + 1;
}
}

fun example() {
let mut sum = 0;
n_times!(10, |x| sum = sum + x );
}

欲瞭解更多資訊,請參閱 巨集 (Macros) 章節。

名稱

函式名稱可以以字母 az 開頭。第一個字元之後,函式名稱可以包含底線 _、字母 az、字母 AZ 或數字 09

fun fOO() {}
fun bar_42() {}
fun bAZ_19() {}

類型參數

在名稱之後,函式可以擁有類型參數:

fun id<T>(x: T): T { x }
fun example<T1: copy, T2>(x: T1, y: T2): (T1, T1, T2) { (copy x, x, y) }

欲瞭解更多細節,請參閱 Move 泛型 (Generics)

參數

函式參數使用本地變數名稱後跟類型標註來宣告:

fun add(x: u64, y: u64): u64 { x + y }

我們將其解讀為 x 的類型為 u64

一個函式可以完全沒有任何參數。

fun useless() { }

這在建立新的或空資料結構的函式中非常常見:

module a::example;

public struct Counter { count: u64 }

fun new_counter(): Counter {
Counter { count: 0 }
}

回傳類型

在參數之後,函式會指定其回傳類型。

fun zero(): u64 { 0 }

這裡的 : u64 表示該函式的回傳類型為 u64

使用 元組 (Tuples),函式可以回傳多個數值:

fun one_two_three(): (u64, u64, u64) { (0, 1, 2) }

如果未指定回傳類型,函式將具有隱式的單元類型 () 作為其回傳類型。以下函式是等價的:

fun just_unit(): () { () }
fun just_unit() { () }
fun just_unit() { }

正如在 元組 (Tuples) 章節 中提到的,這些元組「數值」並不作為執行階段數值存在。這意味著回傳單元類型 () 的函式在執行期間不會回傳任何數值。

函式主體

函式主體是一個表達式區塊。函式的回傳值是序列中的最後一個值:

fun example(): u64 {
let mut x = 0;
x = x + 1;
x // 回傳 'x'
}

有關回傳的更多資訊,請參閱下方章節

有關表達式區塊的更多資訊,請參閱 Move 變數 (Variables)

原生函式 (Native Functions)

有些函式沒有指定主體,而是由虛擬機 (VM) 提供主體。這些函式被標記為 native (原生)。

在不修改 VM 源程式碼的情况下,程式設計師無法添加新的原生函式。此外,native 函式的設計初衷是用於標準程式庫程式碼或該 Move 環境所需的功能。

你可能看到的大多數 native 函式都在標準程式庫程式碼中,例如 vector

module std::vector {
native public fun length<Element>(v: &vector<Element>): u64;
...
}

呼叫

在呼叫函式時,可以透過別名或完全限定名稱來指定函式名稱。

module a::example {
public fun zero(): u64 { 0 }
}

module b::other {
use a::example::{Self, zero};
fun call_zero() {
// 有了上面的 `use` 語句,所有這些呼叫都是等價的
a::example::zero();
example::zero();
zero();
}
}

呼叫函式時,必須為每個參數提供一個實參 (argument)。

module a::example {
public fun takes_none(): u64 { 0 }
public fun takes_one(x: u64): u64 { x }
public fun takes_two(x: u64, y: u64): u64 { x + y }
public fun takes_three(x: u64, y: u64, z: u64): u64 { x + y + z }
}

module b::other {
fun call_all() {
a::example::takes_none();
a::example::takes_one(0);
a::example::takes_two(0, 1);
a::example::takes_three(0, 1, 2);
}
}

類型引數 (Type arguments) 可以被指定,也可以被推導。這兩種呼叫是等價的。

module a::example {
public fun id<T>(x: T): T { x }
}

module b::other {
fun call_all() {
a::example::id(0);
a::example::id<u64>(0);
}
}

欲瞭解更多細節,請參閱 Move 泛型 (Generics)

回傳數值

函式的結果(即其「回傳值」)是其函式主體的最終數值。例如:

fun add(x: u64, y: u64): u64 {
x + y
}

這裡的回傳值是 x + y 的結果。

如前所述,函式主體是一個 表達式區塊。表達式區塊可以包含各種語句的序列,而區塊中最終的表達式將成為該區塊的數值。

fun double_and_add(x: u64, y: u64): u64 {
let double_x = x * 2;
let double_y = y * 2;
double_x + double_y
}

這裡的回傳值是 double_x + double_y 的結果。

return 表達式

函式會隱式地回傳其主體求得的數值。不過,函式也可以使用顯式的 return 表達式:

fun f1(): u64 { return 0 }
fun f2(): u64 { 0 }

這兩個函式是等價的。在這個稍微複雜一點的例子中,函式減去兩個 u64 數值,但如果第二個數值太大則提前回傳 0

fun safe_sub(x: u64, y: u64): u64 {
if (y > x) return 0;
x - y
}

注意,這個函式的主體也可以寫成 if (y > x) 0 else x - y

不過,return 的真正亮點在於從深層巢狀的其他控制流結構中退出。在這個範例中,函式遍歷向量以尋找給定數值的索引:

fun index_of<T>(v: &vector<T>, target: &T): Option<u64> {
let mut i = 0;
let n = v.length();
while (i < n) {
if (&v[i] == target) return option::some(i);
i = i + 1
};

option::none()
}

使用不帶參數的 returnreturn () 的簡寫。也就是說,以下兩個函式是等價的:

fun foo() { return }
fun foo() { return () }