オフィスアワーがそろそろ始まるよ!()

関数

Zenの関数は、他のプログラミング言語と似ています。本書内でも、これまでにいくつかの関数が出てきました。そのうちの1つがmain関数です。main関数はOS上で動くZenアプリケーションで最初に実行する関数です。

pub fn main() anyerror!void {
    std.debug.warn("Hello World.\n", .{});
}

Zenの関数はキャメルケースで命名することが慣習になっています。キャメルケースとは、単語の区切りを大文字にする記述方法です。例を見てみましょう。

fn doSomething() void {
    std.debug.warn("do something!\n", .{});
}

関数の定義

関数はファイルのルートスコープ、構造体の中、列挙型の中、共用体の中、タグ付き共用体の中、に定義することができます。関数の定義は、fnキーワードから開始し、関数名、引数、戻り値型、本体ブロックと続きます。fnキーワードの前に可視性を表すpubをつけることもできます。

pub fn 関数名(引数: 引数型, ... ) 戻り値型 {
    本体ブロック
}

上記の基本構文に加えて、必要に応じて、関数の呼出規約 (calling convension) 、バイトアライメント、リンクセクション、を指定することができます。

可視性 fn(引数: 引数型, ... ) 呼出規約 バイトアライメント リンクセクション 戻り値型 {
    本体ブロック
}

全てを指定すると、次のような関数宣言になります。

pub extern fn reset() callconv(.Unspecified) align(8) linksection(".vector_table.reset_vector") void {
    // ...
}

引数がなく戻り値もない (void) 関数の定義例を示します。Zenでは戻り値がない場合でも、戻り値型の記述を省略することはできないことに注意して下さい。

fn doNothing() void {}

関数を呼び出す

定義した関数は、関数名()で呼び出すことができます。

fn callDoNothing() void {
    doNothing();
}

関数の定義 (もしくは宣言) は、関数呼び出しの後ろにあってもかまいません。Zenではビルド対象となるソースファイル内のどこかで関数が定義されていれば、その関数を呼び出すことができます。

fn callAnother() void {
    anotherFunction();
}

fn anotherFunction() void {}

引数

ノート: 本書内では、仮引数 (parameter) と実引数 (argument)を特別な意図がない限り、どちらも引数と呼びます。

関数は引数を受け取ることができます。u32の引数を受け取る関数は、次のように定義します。Zenでは引数の型を必ず書かなければいけません。

fn printValue(a: u32) void {
    std.debug.warn("value is {}\n", .{a});
}

この関数を呼び出すコードは次の通りです。関数を呼び出すときに、実際の値を与えることができます。

    printValue(5);

実行結果は次のとおりです。

value is 5

引数が渡される時、整数型浮動小数点型の値は常にコピーされ、関数はその値のコピーを受け取ります。次の受け取った引数のアドレスを返す関数を使って、値がコピーされていることを確認してみましょう。

examples/ch04-basic-syntax/src/function.zen:44:46

fn primitiveArgument(x: u32) usize {
    return @ptrToInt(&x);
}

次のテストで呼び出し側と呼び出された側のアドレスが一致していないことをテストします。ok(caller != callee);のテストは無事パスします。

examples/ch04-basic-syntax/src/function.zen:48:54

test "primitive arguments are copied" {
    var x: u32 = 42;
    const caller = @ptrToInt(&x);
    const callee = primitiveArgument(x);

    ok(caller != callee);
}

サイズの大きい配列列挙型構造体共用体は、コピー動作が高価になるため、参照として関数に渡すのが効率的です。これらの型が引数として渡される場合、Zenコンパイラは、引数のコピーを渡すか参照として渡すか、動作が高速な方を選択します。

次のコードを使って検証してみましょう。bigSizeArgument関数の引数型は[10]u32です。この配列の値をコピーするより、参照を渡すほうが効率的でしょう。

examples/ch04-basic-syntax/src/function.zen:56:58

fn bigSizeArgument(x: [10]u32) usize {
    return @ptrToInt(&x);
}

次のテストで、呼び出し側と呼び出された側とで、配列のアドレスが一致することをテストします。ok(caller == callee);のテストは無事パスします。

examples/ch04-basic-syntax/src/function.zen:60:66

test "big size arguments are copied" {
    var x = [_]u32{0} ** 10;
    const caller = @ptrToInt(&x);
    const callee = bigSizeArgument(x);

    ok(caller == callee);
}

関数の引数はイミュータブルであることに注意してください。

fn modifyArgument(a: u32) void {
    a += 1;
}

このコードは、次のコンパイルエラーになります。

error[E02030]: cannot assign to constant variable
    a += 1;
      ~

ノート: 呼出規約がexternに設定されている関数に対しては、C言語のABIに従います。すなわち、構造体と共用体は必ず値渡しします。

戻り値

関数は、自身を呼び出した関数に対して、値を返すことができます。returnキーワードで指定された値が、関数の戻り値となります。

examples/ch04-basic-syntax/src/function.zen:68:75

fn mul(a: u32, b: u32) u32 {
    if (a == 0 or b == 0) {
        // 早期リターン
        // 型は戻り値の型から推論される
        return 0;
    }
    return a * b;
}

呼び出し側の関数では、戻り値を変数に格納して使用することも、そのまま一時的な値として使用することもできます。

examples/ch04-basic-syntax/src/function.zen:77:83

test "return a value" {
    // 戻り値を変数に格納して使用する
    const result = mul(0, 0);
    ok(result == 0);
    // 戻り値を変数に格納せずに使用する
    ok(mul(2, 3) == 6);
}

ただし、戻り値がある関数の戻り値を無視することはできません。

    // 戻り値を使用していない
    mul(3, 4);

上のコードは次のコンパイルエラーになります。

error: expression value is ignored
    mul(3, 4);
       ^

戻り値が不要な場合は、_でその値が不要であることを明示します。

examples/ch04-basic-syntax/src/function.zen:85:87

test "ignore return value" {
    _ = mul(3, 4);
}

戻り値型がvoidの場合、値を指定せずにreturnするか、関数の末尾の文を実行すると、呼び出した関数に制御が戻ります。

examples/ch04-basic-syntax/src/function.zen:89:96

fn returnVoid(a: bool) void {
    if (a) {
        // 早期リターン
        return;
    }
    // 次の`return`は省略可能
    return;
}

エラーを返す

関数からエラーを返す場合は、エラー型を使用します。エラーを返す関数の戻り値型は、エラー型と正常終了時の戻り値型とを合成したエラー共用体です。

例を示します。

examples/ch04-basic-syntax/src/function.zen:103:109

const Error = error {
    AnError,
};

fn returnError() Error!void {
    return Error.AnError;
}

returnError関数の戻り値型はError!voidです。これは、Error型のエラーが返るか、関数が何も返さないことを意味します。

次の例は、Erroru32の値を返す関数です。

examples/ch04-basic-syntax/src/function.zen:117:122

fn returnErrorOrU32(x: u32) Error!u32 {
    if (x == 0) {
        return Error.AnError;
    }
    return x - 1;
}

詳細は3章 エラー型を参照して下さい。

関数の可視性

関数が外部からアクセス可能かどうかを指定することができます。キーワードpubを使って、関数を修飾します。2つの関数の違いは、他のソースコードファイルから関数を呼び出せるかどうか、です。

fn privateFunction() void {}
pub fn publicFunction() void {}

呼出規約

関数にどのように引数を渡すか、戻り値を戻すのか等の定義を呼出規約と呼びます。

callconv(CallingConvention)を指定することで、関数の呼出規約を指定できます。呼出規約は引数の次に記述します。

fn funcName() callconv(.Unspecified) ReturnType

呼出規約は以下の列挙型が指定できます。 呼出規約を指定し、ビルドターゲットがその呼出規約をサポートしていない場合、C言語の呼出規約と見なします。

各呼出規約の詳細は、LLVMのCallingConvを参照してください。

pub const CallingConvention = enum {
    Unspecified,
    C,
    Cold,
    Naked,
    Async,
    Interrupt,
    Signal,
    Stdcall,
    Fastcall,
    Vectorcall,
    Thiscall,
    APCS,
    AAPCS,
    AAPCSVFP,
};

inline指定子

関数を呼び出さずに、呼出箇所に関数の実体を挿入することをインライン展開と呼びます。

Zenのinline指定子はC++のinline指定子と異なり、関数の呼出のインライン展開をLLVMに強制します。 LLVMがインライン展開をせず、関数の実体を作った場合、コンパイルエラーになります。

関数の実体が存在してはならないなど、明確な理由がない限り、C++の様に安易なinlineの指定はしないで下さい。

inline指定子は可視性とfnの間に記述します。

inline fn funcName() ReturnType

Chapter 1

Chapter 2

Chapter 3

Chapter 4

Chapter 5

Chapter 6

Chapter 7

Chapter 8

Chapter 9

Chapter 10

Chapter 11

Chapter 12

Chapter 13

Chapter 14

Chapter 15

Appendix

Error Explanation

☰ 人の生きた証は永遠に残るよう ☰
Copyright © 2018-2020 connectFree Corporation. All rights reserved. | 特定商取引法に基づく表示
Zen, the Zen three-circles logo and The Zen Programming Language are trademarks of connectFree corporation in Japan and other countries.