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

識別子のスコープ

変数や定数といった識別子にはスコープ (有効な範囲) が決められています。ここでは、スコープの種別とその使い方について説明します。一般的には、可能な限り識別子のスコープを狭くするべきです。そうすることで、識別子が誤って使用されるのを防ぐことができます。

Zenは、より外側にある変数を隠す (シャドーイングする) 変数の定義を許しません。次のコードでは、グローバルなxと、関数ローカルなxを定義しようとしています。

const x: u32 = 0;
fn doSomething() void {
    const x: u32 = 0;
}

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

error[E07000]: redeclaration of 'x'
    const x: u32 = 0;
    ~

グローバル

グローバル変数はルートレベルにある変数です。グローバル変数の初期値は、コンパイル時計算可能でなければなりません。グローバル変数がconstの場合、その値はコンパイル時計算可能な変数として利用できます。varは初期値がコンパイル時に決まる、実行時計算可能な値という扱いになります。

下のコードで、var global1: u32 = constant + 1;constant + 1がコンパイル時計算可能であるためコンパイルが通りします。しかしvar global2: u32 = global1 + 1;は、global1 + 1が実行時計算可能になるため、コンパイルエラーになります。

examples/ch04-basic-syntax/src/scope.zen:4:14

const constant: u32 = 0;
var global1: u32 = constant + 1;
// Compile error: unable to evaluate constant expression
// var global2: u32 = global1 + 1;

test "global variables" {
    comptime {
        ok(constant == 0);
    }
    ok(global1 == 1);
}

構造体、共用体、列挙型の中にもグローバル変数が定義できます。

examples/ch04-basic-syntax/src/scope.zen:16:18

const Struct = struct {
    var global_in_struct: u32 = 0;
};

このことを応用して、ある関数内からしか触ることのできないグローバル変数を作ることができます。

examples/ch04-basic-syntax/src/scope.zen:69:81

fn cStatic() u32 {
    const CStatic = struct {
        var value: u32 = 0;
    };

    CStatic.value += 1;
    return CStatic.value;
}

test "c static" {
    ok(cStatic() == 1);
    ok(cStatic() == 2);
}

ローカル

関数およびブロック内に定義した変数はローカル変数です。ローカル変数はそのスコープを越えて存在することはできません。

examples/ch04-basic-syntax/src/scope.zen:28:43

fn functionScope() void {
    const function_scope: u32 = 1;

    {
        const block_scope: u32 = 2;
        // 中のスコープでは外のスコープの変数を利用できる
        ok(block_scope + function_scope == 3);
    } // `block_scope`が利用可能なのはここまで

    // Compile error: use of undeclared identifier 'block_scope'
    // ok(block_scope == 2);
}

// Compile error: use of undeclared identifier 'function_scope'
// `function_scope`は`functionScope`関数の中でのみアクセス可能
// const y: u32 = function_scope;

スレッドローカル

threadlocalキーワードを付けた変数はスレッドローカル変数になります。すなわち、スレッドごとに異なるメモリ領域を持ちます。別スレッドがスレッドローカルな変数に行った変更は、自身のスレッドローカルな変数に影響を与えません。

examples/ch04-basic-syntax/src/scope.zen:49:67

threadlocal var thread_scope: u32 = 1;

fn incThreadLocal(context: void) void {
    ok(thread_scope == 1);
    thread_scope += 1;
    ok(thread_scope == 2);
}

test "thread local variable" {
    incThreadLocal({});
    ok(thread_scope == 2);

    // 別スレッドを立ち上げて`thread_scope`を変更する
    const thread = try std.threading.Thread.spawn({}, incThreadLocal);
    thread.wait();

    // 別スレッドで`thread_scope`を変更しても影響しない
    ok(thread_scope == 2);
}

スレッドローカルなconstを作ることは意味を為さないでしょう。イミュータブルな値を読み込むだけであれば、グローバルなconstで十分なためです。

@import

組込み関数の@importは別ソースファイル中でpubキーワードをつけて宣言された変数や関数を、インポートしたソースファイルから利用できるようにします。Zenのソースファイルは、暗黙的に構造体として扱われます。この暗黙の構造体は、名前空間と呼べるでしょう。多くの場合、この名前空間に新しい名前を定義します。インポートしたソースファイル内の変数は、その名前空間内にスコープを持ちます。

const namespace = @import("source.zen");

@importの引数には、Zenのソースファイルを、絶対パスもしくは相対パスで与えます。stdbuiltinだけは特別で、どこからでも@import("std")@import("builtin")でインポートできます。

型エイリアス

ある型に対して、別名をつけることができます。これを型エイリアスと呼びます。例えば、[]u8Stringというエイリアスを付けるには、次のようにします。

const String = []u8;

型エイリアスは、エイリアスが定義されたスコープ内でのみ有効です。

usingnamespace

@importでインポートした場合、メンバアクセス演算子 (.) と使用して、名前空間.関数名前空間.変数といった形で、pubな関数や変数を利用します。しかし、インポートする名前空間内全てのpubアイテムをそのまま使いたい場合があります。

そのような時はusingnamespaceを利用できます。

例えば、今次のようなadd_func.zenがあるとします。

pub fn add(a: u32, b: u32) u32 {
    return a + b;
}

このadd関数を、名前空間を通さずに利用するには、次のようにします。

usingnamespace @import("add_func.zen");
test "usingnamespace" {
    ok(add(1, 2) == 3);
}

☰ 人の生きた証は永遠に残るよう ☰
Copyright © 2018-2019 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.