変数の基本

Basis
最終更新:

変数(variable)はほぼすべてのプログラムの基礎だ。変数がなければ、プログラムは一つの決まったことしかできないが、変数があれば任意のデータを扱い、入力に適応して、実行時に動作を変えられる。Zig では変数は設計上明示的で厳格であり、コードが実行される前にバグを見つけやすくなっている。

変数とは何か?

変数とは、値を保持するメモリ上の名前付き場所のことだ。同じ生の数値やフラグをあちこちに書く代わりに、後でコード内でその値を参照できるよう名前を付ける。

ラベルの付いた箱のように考えるとよい:ラベルが名前、中身が値、箱の形が型(type)だ。はプログラムに何バイトのメモリを確保すべきか、そのバイトをどう解釈すべきかを伝える。

Zig での変数宣言

Zig には二種類の変数宣言がある:

  • constイミュータブルな束縛(immutable binding)を宣言する:一度代入したら値は変えられない。
  • varミュータブルな束縛(mutable binding)を宣言する:後で値を再代入できる。

どちらも同じ基本形に従う:

const name: Type = value;
var   name: Type = value;

型はコロンで区切られて名前のに来ることに注意しよう。具体的な例を示す:

const speed_of_light: u64 = 299_792_458; // イミュータブル、メートル毎秒
var   score: i32 = 0;                    // ミュータブル、ゼロから始まる
score = 100;                             // var の場合は再代入が可能

いくつか覚えておくこと:

  • 数値リテラルには可読性のためにアンダースコア(299_792_458)を含められる — コンパイラーはそれを無視する。
  • すべての変数は宣言時に初期化しなければならない。Zig は値なしで変数を残すことを許可しない。
  • var を宣言して実際には変更しない場合、コンパイラーはコンパイルを拒否して代わりに const を使うよう指示する。Zig はコードを正直に保つためこれを強制する。

デフォルトでは const を使うのが望ましい。本当に変更が必要な値だけに var を使う。

基本的な型の概要

Zig は静的型付き(statically typed)言語だ:すべての変数はコンパイル時に判明し、決して変わらない固定の型を持つ。このチェックポイントで紹介する 4 つのカテゴリを示す。

整数

整数(integer)は小数部のない整数値だ。Zig の整数型は符号(sign)とビット幅(bit width)を型名に直接エンコードしている:

符号おおよその範囲
u8なし(符号なし)0 〜 255
i8あり(符号あり)−128 〜 127
u32なし0 〜 約 43 億
i32あり約 −21 億 〜 約 21 億
u64なし0 〜 約 18.4 × 10¹⁸
i64あり約 ±9.2 × 10¹⁸
usizeなしポインターサイズ(プラットフォーム依存)

符号あり整数は負の数を保持できる;符号なし整数は保持できないが、同じビット数でより多くの正の値を表現できる。プレフィックス u は符号なし、i は符号ありを意味し、数字はビット数だ。

const max_players: u8    = 255;
var   temperature: i32   = -10;
var   count:       usize = 0;

汎用的なカウントには i32 または u32 がよく使われる。配列のインデックスに usize をよく見かけるだろう。整数演算、オーバーフロー(overflow)の挙動、型キャストの詳細は後のチェックポイントで扱う。

浮動小数点数

浮動小数点数(float)は小数部を持つ実数を表現する — 3.14-0.56.022e23 のような値だ。Zig の浮動小数点型は精度を型名にエンコードしている:

ビット幅有効十進桁数
f3232約 7 桁
f6464約 15〜16 桁
f128128約 34 桁
const pi:          f64 = 3.14159265358979;
var   temperature: f32 = -4.5;
const avogadro:    f64 = 6.022e23;        // 科学的記数法も有効

ほとんどの用途には f64 が適切なデフォルトだ。メモリが限られていて精度の低下が許容できる場合(グラフィクスや組み込みシステムでよくある)に f32 を使う。浮動小数点リテラルは小数点か指数のどちらかを含まなければならない — 6.022e23 はピリオドがなくても条件を満たす。

浮動小数点数には重要な癖がある:すべての実数を正確に表現できるわけではなく、演算結果が静かに丸め誤差を蓄積することがある。Zig のすべての浮動小数点型、特殊な値(NaNInf)、比較の落とし穴、型キャストを含む詳細な解説は Float Number チェックポイントにある。

真偽値

真偽値(boolean)の booltrue または false の二値のうちいずれかを保持する。真偽値は比較の自然な出力であり、if 条件の自然な入力だ。

const is_running: bool = true;
var   has_error:  bool = false;

分岐と論理演算子での役割は制御フローのチェックポイントで詳しく学ぶ。

ポインター

ポインター(pointer)は値そのものではなく、別の値のメモリアドレスを格納する。Zig では *T 型は「型 T の値へのポインター」を意味する。

var   x:  i32  = 42;
const px: *i32 = &x;  // px は x のアドレスを保持する

& 演算子は変数のアドレスを生成する。ポインターが指す値を読み書きするには .* サフィックスを使う:

const value = px.*;   // 参照外し(dereference):px に格納されたアドレスから 42 を読む

ポインターは強力だが注意が必要だ。このチェックポイントでは構文の紹介に留める;セマンティクス — *const T*T、null 安全性、ポインター演算 — はこのチェックポイントを前提条件とするポインターのチェックポイントで扱う。

変数を出力する

開発中に変数の値を観察する最もシンプルな方法は出力することだ。Zig の標準ライブラリには std.debug.print が用意されており、追加設定なしで標準エラーストリームに直接書き込む。

const std = @import("std");

pub fn main() void {
    const score: i32 = 42;
    std.debug.print("score = {}\n", .{score});
}

このプログラムを実行すると次が出力される:

score = 42

各部分が何をしているかを分解して説明する:

  • @import("std") は標準ライブラリを読み込んで std という名前に束縛する。
  • std.debug.print の最初の引数はフォーマット文字列(format string)だ。{} プレースホルダーは対応する変数の値に置き換えられる。
  • 第二引数 .{score}匿名構造体リテラル(anonymous struct literal)— 可変長の値リストをフォーマッターに渡す Zig の方法だ。
  • \n は改行文字で、カーソルを次の行に移動する。

同じ {} プレースホルダーはすべての単純な型に対して動作する:

const std = @import("std");

pub fn main() void {
    const flag: bool = true;
    var   x:    i32  = 7;
    const pi:   f64  = 3.14;
    const px:   *i32 = &x;

    std.debug.print("flag  = {}\n",  .{flag});  // flag  = true
    std.debug.print("x     = {}\n",  .{x});     // x     = 7
    std.debug.print("pi    = {}\n",  .{pi});    // pi    = 3.14
    std.debug.print("px    = {}\n",  .{px});    // px    = (メモリアドレス)
    std.debug.print("px.*  = {}\n",  .{px.*});  // px.*  = 7
}

スコープ

Zig のすべての変数はスコープ(scope)の中に存在する — 変数が見えて有効なコードのブロックだ。スコープは波括弧 {} で区切られる。

pub fn main() void {
    const a: i32 = 1;     // 'a' は外側のスコープに存在する

    {
        const b: i32 = 2; // 'b' はこの内側のスコープに存在する
        _ = a;            // 外側の変数は内側から見える
        _ = b;
    }                     // ここで 'b' は破棄される

    _ = a;                // 'a' はまだ有効
    // _ = b;             // エラー:'b' はここではスコープ外
}

_ = expr; は値を明示的に捨てる Zig の方法だ。コンパイラーはすべての計算された値が使用されるか明示的に捨てられることを要求するため、スコープに焦点を当てた例でよく見かけるパターンだ。

実行がスコープを抜けると、その中で宣言されたすべての変数が破棄される。これは単なる可視性のルールではない — 値のライフタイム(lifetime)を決定するもので、ポインターとメモリ確保を扱い始めると非常に重要になる。

覚えておくべき三つのルール:

  • 内側のスコープは外側のスコープの変数を読める
  • 外側のスコープは内側のスコープの変数を見られない
  • シャドーイング(shadowing)— 外側のスコープにあるものと同じ名前の新しい変数を宣言すること — は Zig で許可されているが、控えめに使うこと。バグを簡単に隠してしまう可能性がある。

まとめ

  • 変数とは型付きの値を保持する名前付きメモリ場所だ。
  • const はイミュータブルな束縛を宣言し、var はミュータブルな束縛を宣言する。デフォルトでは const を使う。
  • 型アノテーションは名前の後に来る:const x: i32 = 0;。すべての変数は初期化が必要だ。
  • この段階での 4 つのコア型:
    • 整数i32u32usize など)— 符号とビット幅が明示的な整数値。
    • 浮動小数点数f32f64 など)— 小数部を持つ実数;すべての値を正確に表現できるわけではない。
    • 真偽値bool)— true または false
    • ポインター*T)— 型 T の値のメモリアドレス。
  • 単純な変数の値を標準エラーに出力するには std.debug.print("{}\n", .{x}) を使う。
  • 変数は { } で区切られたスコープの中に存在する。内側のスコープは外側の変数を見られるが、外側のスコープは内側の変数を見られない。変数はスコープが終わると破棄される。