Open
72

ziglings メモ

zig の言語サーバーの zls を試しに使ってみたくて、zig に興味持った

rustlings 的なものがあるらしいから、ちょっとやってみる

ただやるだけじゃなくて、メモとして残しておく。

続くかな?気分で進めよう。

まずは、zig の開発版をインストールする

スクリプトでエイヤッと入れる

#!/bin/bash

function zig_latest() {
    fname=zig-linux-x86_64-0.8.0-dev.1761+ab5a445d2.tar.xz
    cd /tmp
    curl -sS -O https://ziglang.org/builds/$fname
    tar -xvf $fname
    mv $fname $HOME/.local/share/zig
    sudo ln -snf $HOME/.local/share/zig/zig /usr/local/bin/zig
    rm -rf zig-*
}

zig_latest

$ ghq get ratfactor/ziglings

cd して、

$ zig build

すると

❯ zig build

         _       _ _
     ___(_) __ _| (_)_ __   __ _ ___
    |_  | |/ _' | | | '_ \ / _' / __|
     / /| | (_| | | | | | | (_| \__ \
    /___|_|\__, |_|_|_| |_|\__, |___/
           |___/           |___/

Compiling 001_hello.zig...
/home/tamago324/.local/share/zig/lib/std/start.zig:330:45: error: 'main' is private
    switch (@typeInfo(@typeInfo(@TypeOf(root.main)).Fn.return_type.?)) {
                                            ^
./exercises/001_hello.zig:19:1: note: declared here
fn main() void {
^
001_hello.zig: The following command exited with error code 1:
/home/tamago324/.local/share/zig/zig build-exe /home/tamago324/ghq/github.com/ratfactor/ziglings/exercises/001_hello.zig --cache-dir /home/tamago324/ghq/github.com/ratfactor
/ziglings/zig-cache --enable-cache

HINT: DON'T PANIC!
Read the error above.
See how it has something to do with 'main'?
Open up the source file as noted and read the comments.
You can do this!
Edit exercises/001_hello.zig and run this again.
To continue from this zigling, use this command:
    zig build 1

error: 'main' is private って書いてあるから、main がプライベートになっているから、パブリックにしてねっってことだ

HINT: DON'T PANIC!
Read the error above.
See how it has something to do with 'main'?
Open up the source file as noted and read the comments.
You can do this!
Edit exercises/001_hello.zig and run this again.
To continue from this zigling, use this command:
zig build 1

google 翻訳に頼ると

ヒント:あわてないで!
上記のエラーを読んでください。
それが「main」とどのように関係していますか?
記載されているように exercises/001_hello.zig を開き、コメントを読んでみてください。
あなたはこれを行うことができます!
`exercises/001_hello.zig を編集し、これを再実行する。
この ziglings を続行するには、次のコマンドを使います。
zig build 1

ってかいてある

編集してみる

const std = @import("std");

pub fn main() void {
    std.debug.print("Hello world!\n", .{});
}

んで、

$ zig build 1

を実行すると

❯ zig build 1
Compiling 001_hello.zig...
Checking 001_hello.zig...
PASSED: Hello world!

お!できた

手順としては、

  1. zig build 番号
  2. 編集
  3. zig build 番号

で進めていく

001_hello.zig

"Hello world!" を表示するプログラム

zig の関数はデフォルトでプライベートで定義される
でも、main() 関数は公開されている必要がある

以下のように "pub" をつけると、公開できる

pub fn foo() void {
  ...
}

002_std.zig

Hello World を表示するプログラムだけど、zig の標準ライブラリをインポートする方法をわすれちゃった。
@import() 関数は zig に組み込まれていて、インポートされたコードを表す値を返す。
インポートしたものと同じ名前にするのがいいらしい

インポートは実行時に評価されるのではなく、コンパイル時に評価されるため、@import() 関数の戻り値は const で受け取る必要があるらしい

003_assignment.zig

変数定義のキーワードは2つ

  • const: 不変
  • var: 可変

u8 の意味について

u8i8 の u と i はそれぞれ、 「uunsigned (符号なし)」と、「i は符号あり」の意味。
また、u88 はビット数となっていて、例えば、u8 だと、0b11111111 で 0から255 まで保持できる。

004_arrays.zig

配列について学ぶ

通常の宣言は、型と要素数を、変数と値に [要素数]型 のように指定する

  var foo: [3]u32 = [3]u32{1, 2, 10};

配列のサイズが推測可能な場合、要素数は _ でかける。
また、値から型が推測可能な場合、型が省略できる。

  // こんな感じでかける!
  var foo = [_]u32{ 1, 2, 10};

要素の取得とセットは普通にできる。1-base index となっている。


配列の長さは len プロパティを使って取得可能。

  const length = foo.len;

const を使って配列を宣言すると、arr[0] = 100; のように変更できない

だから、変更するかもしれない配列はについては、var を使って宣言する

005_array2.zig

zig には、楽しい配列の演算子があるらしい


++ で配列の連結ができる

    const a = [_]u8{ 1, 2 };
    const b = [_]u8{ 3, 10 };
    const c = a ++ b ++ [_]u8{ 100, 200 }; // [_]u8{ 1, 2, 3, 10, 100, 200 }

** で繰り返せる

    const x = [_]u8{ 1, 2 } ** 3; // [_]u8{ 1, 2, 1, 2, 1, 2}

for で *n のようにしていたのは、なんで?

    for (leet) |*n| {
        std.debug.print("{}", .{n.*});
    }

n でもループできるのに...

    for (leet) |n| {
        std.debug.print("{}", .{n});
    }

006_strings.zig

文字列の話。
配列と関わりがある。
なぜなら、zig は文字列をバイトの配列として扱うから。

以下の2つは同じになる

  // 文字列リテラル
  const s = "Hello";
  // u8 の配列
  const s2 = [_]u8{ 'H', 'e', 'l', 'l', 'o' };

文字列リテラルに対しても、配列演算子の **++ が使える

  • **: 文字列の繰り返し
  • ++: 文字列の連結

std.debug.print(){} に値の情報をかける。

  • {u}: UTF-8 character
  • {s}: UTF-8 strings

もし、{} だけにすると、10進数として表示しようとする。そのため、文字列を渡すとエラーになる。
(std.debug.print("d={u} {s}{}\n", .{ d, laugh, major_tom }); としたら、エラーになった)

007_strings2.zig

先頭に \\ を書くことで複数行の文字列が記述できる
\\ hello とかくと、先頭にスペースが入った文字列 (" hello") になるため、注意が必要

(tree-sitter がバグっているけど、わからなかった...)

008_quiz.zig

undefined でなんでも無い値として変数を初期化して、宣言できる。

zig の docs を見てみる

undefined を使うことで、変数を初期化しないで宣言できる。
undefined は、値が何でもあり得ることを示すために使う。
使う前に初期化されるであろうな値らしい。

009_if.zig

zig の if は真偽値 のみ 受け取る
いいなこれ。
だから、1 が true とか nullfalse とかはならないってこと。
明示的に比較する。

010_if2.zig

参考演算子みたいにかける
if が式として有効な場合、if 式としてかける?
というか、docs だと、if って書いてある

const price = if (discount) 17 else 20;

docs にはたくさん例がある。

null かどうかによって、処理を変えたいときにも if 文が使える。

    const a = null;
    if (a) |value| {
        std.debug.print("a is not null", .{});
    } else {
        // null のため、こっちに入る
        std.debug.print("a is null", .{});
    }

もし、null ではないときには、キャプチャできる

    // ? をつけないと、error: expected optional type, found 'u8' って怒られた
    // if (b) |value| { ... } って書いているから、b の型はオプションじゃないといけないのかも?
    const b: ?u8 = 100;
    if (b) |value| {
        std.debug.print("b is {}", .{b});
    } else {
        std.debug.print("b is null", .{});
    }

011_while.zig

while 文は条件が真である間実行するループを作成する
条件式は真偽値じゃないとだめ


docs

012_while2.zig

zig の while では、ループが実行されるたびに実行される continue 式 を指定できる。
これ面白い!

// ループするたびに i がインクリメントされる
while (i < 10) : (i += 1) {
    std.debug.print("{}", .{i});
}

複数の式を評価できる。
while (条件式) : ({ 式1; 式2; ... ;}) { ... }

    var i: u8 = 0;
    while (i < 10) : ({
        i += 1;
        i += 1;
    }) {
        std.debug.print("{}\n", .{i});
    }

013_while3.zig

continue 文があるかないかに関わらず、while の continue 式 は評価される。

014_while4.zig

break で while のループを中断できる。
break で中断されたとき、continue 式 は実行されない。

015_for.zig

for でループできる
配列とか、文字列とかループできる

    const numbers = [_]u8{ 1, 3, 5 };
    for (numbers) |n| {
        std.debug.print("{} ", .{n});
    }

016_for2.zig

for で、ループのインデックスを保存できる (Python でいう enumerate() みたいな)
0 始まり。

    const numbers = [_]u8{ 1, 3, 5 };
    for (numbers) |n, idx| {
        std.debug.print("{}: {}\n", .{ idx, n });
    }

// 0: 1
// 1: 3
// 2: 5

017_quiz2.zig

fizzbuzz のプログラムを修正する

018_functions.zig

fn を使って、関数定義する
pub をつけると公開される

fn sum(a: u8, b: u8) u8 {
  return a + b;
}

019_functions2.zig

引数は fn fun1(仮引数名: 型, 仮引数名: 型) { ... } のように書く

020_quiz3.zig

forwhile を使う。
あと関数の型も

021_errors.zig

zig ではエラーは値として扱われる。
エラーはエラーセットで作られる。
これは単なる名前付きのコレクション (Enum みたいなもの)

const コレクション名 = error{
  名前1,
  名前2,
  ...
};

エラーが値だから、戻り値に指定できる!

fn sampleFail(n: u8) MyNumberError {
  ...
  return MyNumberError.TooFour;
}

022_errors2.zig

zig では「エラーユニオンタイプ (Error Union Type)」と呼ばれるものがある。
Error Union Type を使うことによって、Error Type か普通の型のどちらかになるということを示せる。
Error Union Type は ! 演算子 (二項演算子) を使って表現できる。

// MyError と u8 の Error Union Type
var i: MyError!u8 = undefined;

u8!MyError だとエラーになってしまったため、エラーを先に書く。

023_errors3.zig

Error union を処理する1つの方法は catch してデフォルト値に変換することらしい。
「エラーだった場合の値を指定できる」ってこと。

const std = @import("std");

const MyNumberError = error{zeroDivisionError};

pub fn main() void {
    var res1: u8 = div(100, 2) catch 0;
    // MyNumberError.zeroDivisionError が起きたら、 catch して 0 とする
    var res2: u8 = div(100, 0) catch 0;

    std.debug.print("{}\n", .{res1});
    std.debug.print("{}\n", .{res2});
}

fn div(a: u8, b: u8) MyNumberError!u8 {
    if (b == 0) {
        // 0 だったらエラーにする
        return MyNumberError.zeroDivisionError;
    }

    return a / b;
}

024_errors4.zig

エラーがあった時に catch でデフォルト値に変換するときは、エラーは何でもいいってこと。

エラーをキャッチして、そのエラーによって処理を変えたいときにも catch は使える。

fn fixTooSmall(n: u32) MyNumberError!u32 {
    // エラーが発生して、TooSmall だったら、10にする
    // その他のエラーになったら、そのエラーを返す
    // 数値なら、その数値を返す
    return detectProblems(n) catch |err| {
        // エラーが返されたときのみ、ここに入る
        if (err == MyNumberError.TooSmall) {
            return 10;
        } else {
            return err;
        }
    };
}

025_errors5.zig

try 便利かも?
try canFail()canFail() catch |err| return err; と同じ意味

const std = @import("std");

const MyNumberError = error{zeroDivisionError};

pub fn main() void {
    var res1: u8 = divTry(100, 2) catch 0;
    var res2: u8 = divTry(100, 0) catch 0;
}

fn divTry(a: u8, b: u8) MyNumberError!u8 {
    // try なら、エラーになったら、そこで return される。
    var x = try div(a, b);

    // エラーになったら、ここは実行されない。
    std.debug.print("no error\n", .{});

    return x;
}

fn div(a: u8, b: u8) MyNumberError!u8 {
    if (b == 0) {
        // 0 だったらエラーにする
        return MyNumberError.zeroDivisionError;
    }

    return a / b;
}

026_hello2.zig

標準出力を使う

!void と書くと、エラーを推測してくれる。

標準出力を使って、出力するときには、エラーで失敗する可能性があるため、main() の戻り値で返せるようにしておきたい。そのため、!void とした。

エラーがあったらそのエラーをそのまま返したいときは、 try を使って try stdout.print("Hello world!\n", .{}); とする

027_defer.zig

defer を使って、そのスコープの最後に処理を実行できる

docs

const std = @import("std");

pub fn main() void {
    var a: u8 = 10;
    {
        defer a = 100;
        std.debug.print("{}\n", .{a});
    }
    std.debug.print("{}\n", .{a});
}

// 10
// 100

これ、いつ使うんだろう...?
他の人のコードを読まないと!!

029_errdefer.zig

docs

errdefer はブロックでエラーで終了したときのみ実行される defer
クリーンアップのための処理とか?

ふーんって感じ

030_switch.zig

switch

const std = @import("std");

pub fn main() void {
    var l = [_]u8 {0, 1, 1, 2, 0};

    for (l) |v| {
        switch (v) {
            0 => std.debug.print("zero~\n", .{}),
            1 => std.debug.print("one~\n", .{}),
            // else が必要
            else => std.debug.print("other~\n", .{})
        }
    }
}

// zero~
// one~
// one~
// other~
// zero~

031_switch2.zig

switch は値を返すことができるらしい。
面白い

const std = @import("std");

pub fn main() void {
    var s = "hello world!";

    for (s) |v| {
        var upper: u8 = switch (v) {
            'h' => 'H',
            'e' => 'E',
            'l' => 'L',
            'o' => 'O',
            ' ' => ' ',
            else => '?',
        };
        std.debug.print("{c}", .{upper});
    }

}

// HELLO ??O?L??

std.debug.print(){} とすると数値を出力しようとする。
文字を表示したい場合、{c} を使用し、
文字列を表示したい場合、{s} を使用する。


// o は8進数
std.debug.print("{o}", .{9});  // 11

032_unreacable.zig

unreacable 文は、そこに達してはいけないことを表すためのもの。
Rust にもあったっけ。

これは、Debug モード、 ReleaseSafe モード、 zig test のときにパニック?を起こしてくれる。

unreacahble は、そこへは到達しないことを表明するためにも使える (らしい)

docs

もし、unreachable 文に達してしまった場合、

panic: reached unreachable code

というパニック?が発生する。

const std = @import("std");

pub fn main() void {
    const arr = [_]u8{ 2, 4, 5, 6 };

    for (arr) |v| {
        if (v % 2 == 0) {
            std.debug.print("{} は偶数です\n", .{v});
        } else {
            unreachable;
        }
    }
}

5 で reaced unreachable code というパニックが発生している。

2 は偶数です
4 は偶数です
thread 654266 panic: reached unreachable code
/home/tamago324/ghq/github.com/tamago324/sandbox-zig/sandbox/unreachable_sample.zig:10:13: 0x22d4fc in main (unreachable_sample)
            unreachable;
            ^
/home/tamago324/.local/share/zig/lib/std/start.zig:335:22: 0x204e1e in std.start.posixCallMainAndExit (unreachable_sample)
            root.main();
                     ^
/home/tamago324/.local/share/zig/lib/std/start.zig:163:5: 0x204cf2 in std.start._start (unreachable_sample)
    @call(.{ .modifier = .never_inline }, posixCallMainAndExit, .{});
    ^

033_iferror.zig

if では、エラーのテストが行える。

if (errorOrOk) |value| {
    // エラーではないとき
} else |err| {
    // エラーのとき
}

elseswitch を組み合わせてかけるの面白い

if (n) |value| {
  ...
} else |err| switch (err) {
  MyNumberError.TooBig => ...,
  MyNumberError.TooSmall => ...,
}

034_quiz4.zig

クイズ。

const std = @import("std");

const NumError = error{IllegalNumber};

// stdout.print() でエラーが起きるかもだから、 !void とした
pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    // getNumber() がエラーを返すかもしれないため、 catch でデフォルト値を指定してみた。
    const my_num: u32 = getNumber() catch 42;

    try stdout.print("my_num={}\n", .{my_num});
}

// Just don't modify this function. It's "perfect" the way it is. :-)
fn getNumber() NumError!u32 {
    if (false) return NumError.IllegalNumber;
    return 42;
}

035_enums.zig

enum すばらしい。
数値に名前をつけてまとめて管理できる。 error set によく似ている。 (error set が enum に似ているのか、どっちかは知らんけど)
ただの数値のまとまりじゃなくて、列挙型 (enum) を使おうねってことかな?

const std = @import("std");

// 方角
const Direction = enum{
    /// 北
    north,
    /// 南
    south,
    /// 東
    east,
    /// 西
    west,
};

pub fn main() void {
    const directions = [_]Direction {
        Direction.north,
        Direction.south
    };
    for (directions) |d| {
        std.debug.print("{}\n", .{d});
    }
}

// Direction.north
// Direction.south

036_enums2.zig

enum の番号はコンパイラが自動採番する。でも、明示的に値を指定することも可能。
また、数値の型を指定することもできる。

@enumToInt() 関数で enum の数値を取得できる。
組み込み関数は @ で始まる。

0xで始めると16進数で数値をかける。

std.debug.print() でフォーマットを指定できる。
{x:0>6} とすると

  • x は 16進数
  • 0 は0埋め (デフォルトは ' ')
  • > は右詰め
  • 6 は6桁で埋める
    ということになる

037_structs.zig

struct (構造体) で値をまとめられる。

ロールプレイングのキャラクターを表現するらしい。なんか楽しい。

const Character = struct {
    gold: u32,
    health: u8,
};

// {} の中で . を使っているの違和感あったけど、.プロパティって考えるとしっくりきた
var taro = Character {
    .gold = 500,
    .health = 100,
};

docs

038_structs2.zig

構造体は1つの型だから、配列の要素としても扱える。

039_pointers.zig

ポインタ:値への参照

*u8u8 の値へのポインタ型
&foofoo への参照
foo.*foo への逆参照 (deref)

    // 値
    var a: u8 = 6;
    // 値への参照
    var a_pointer: *u8 = &a;

    var b: u8 = undefined;

    // 逆参照 (取得?)
    b = a_pointer.*;

    std.debug.print("a: {}, b: {}", .{ a, b });  // a: 6, b: 6

040_pointer2.zig

変数ポインタと定数ポインタは別の型であることに注意!

const std = @import("std");

pub fn main() void {
    // 定数ポインタと変数ポインタの型は異なる

    const a: u8 = 12;
    const b: *const u8 = &a;

    std.debug.print("{}\n", .{@TypeOf(&a)}); // *const u8
    std.debug.print("{}\n", .{@TypeOf(a)}); // u8

    var c: u8 = 100;
    var d: *u8 = &c;
    std.debug.print("{}\n", .{@TypeOf(&c)}); // *u8
    std.debug.print("{}\n", .{@TypeOf(c)}); // u8
}

また、
変数への定数ポインタは作成できる (可変 → 不変 はOK)

const a: u8 = 100;
// *const u8 が定数ポインタ。
const b: *const u8 = &a;

けど、
定数への変数ポインタは作成できない (不変 → 可変 はNG)

const a: u8 = 100;
// *u8 は変数ポインタだから、エラーになる
const c: *u8 = &a;  // error: expected type '*u8', found '*const u8'

ポインタ変数自体の可変性 (var か const か) は、
ポインタが参照している値の可変性とは関係ない。

こういうこと

fn pointerSample() void {
    const locked: u8 = 100;
    // p1 自体は var だけど、定数ポインタ
    var p1: *const u8 = &locked;

    const p2: *const u8 = &locked;
    // p2 自体が const で定義されているため、p2 への再代入はできない。
    // p2 = &hogehoge;
}

041_pointer3.zig

変数 再代入 参照先の値の変更
p1 x x
p2 o x
p3 x o
p4 o o
p5 x x
p6 o x
    const locked: u8 = 4;
    var unlocked: u8 = 10;

    const p1: *const u8 = &locked;
    var p2: *const u8 = &locked;

    const p3: *u8 = &unlocked;
    var p4: *u8 = &unlocked;

    // &unlocked は *u8 だけど、 *const u8 にできる。
    const p5: *const u8 = &unlocked;
    var p6: *const u8 = &unlocked;


    const a: u8 = 100;

    // p1.* = 10;
    // p2.* = 10;
    p2 = &a;

    p3.* += 10; // 20
    std.debug.print("{}\n", .{p3.*});

    p4.* += 20; // 40
    std.debug.print("{}\n", .{p4.*});

    var b: u8 = 100;
    // p4 は *u8 型だから、 a の参照は入れられない (*const u8 だから)
    // p4 = &a;
    p4 = &b;
    std.debug.print("{}\n", .{p4.*}); // 100

    // p5.* += 10;
    p6 = &b;

042_pointers4.zig

関数に変数の参照を渡して、その仮引数に対して、値をセットすることで呼び出し元に値を渡せる。

// ポインタを受け取る
fn pow(x: *u8) void {
    // アドレスの場所にある値を更新
    x.* = x.* * x.*;
}

pub fn main() void {
    var x: u8 = 10;

    // アドレスを渡す
    pow(&x);

    std.debug.print("{}", .{x});
}

043_pointers5.zig

構造体のフィールドにアクセスするために逆参照する必要はない
(これはよくやるから、省略できるってこと?)

    const Vertex = struct{ x: u32, y: u32, z: u32 };
    var v1 = Vertex{ .x=3, .y= 2, z=5 };
    var pv: *Vertex = &v1;

    // pv.*.x ではない。
    std.debug.print("{}\n", .{pv.x});

列挙型で switch するときには、省略してかける。

const std = @import("std");

const Color = enum { red, yellow, blue };

pub fn main() void {
    printColor(Color.blue);
    printColor(Color.red);
}

fn printColor(c: Color) void {
    const color_str = switch (c) {
        // .red は Color.red と解釈してくれる
        .red => "RED",
        .yellow => "YELLOW",
        .blue => "BLUE"
    };

    std.debug.print("{s}\n", .{color_str});
}

044_quiz5.zig

const std = @import("std");

const Elephant = struct {
    letter: u8,
    tail: *Elephant = undefined,
    visited: bool = false,
};

pub fn main() void {
    var elephantA = Elephant{ .letter = 'A' };
    var elephantB = Elephant{ .letter = 'B' };
    var elephantC = Elephant{ .letter = 'C' };

    // ぐるぐるつなげる
    elephantA.tail = &elephantB;
    elephantB.tail = &elephantC;
    elephantC.tail = &elephantA;

    visitElephants(&elephantA);

    std.debug.print("\n", .{});
}

fn visitElephants(first_elephant: *Elephant) void {
    // なぜ、これをやるのか?
    // e に再代入したいから
    var e = first_elephant;

    // e.*.visited ではない
    while (!e.visited) {
        std.debug.print("Elephant {u}. ", .{e.letter});
        e.visited = true;

        // &e.tail のように & はつけないの???
        // elephantA.tail = &elephantB; ってやってるから、いらない!
        e = e.tail;
    }
}

仮引数の変数は const なのかな?

error: cannot assign to constant

っていうエラーになる。
だから、一回、 e に入れている。

045_optionals.zig

オプションだから、あってもなくてもいい値ってことかな?

型の前に ? をつけるとその型をオプション型に変換できる。

const std = @import("std");

pub fn main() void {
    const normal_int: i32 = 100;
    // i32 か null のどちらかが入る。
    const optional_int: ?i32 = 234;
}
const std = @import("std");

pub fn main() void {
    var optional_int: ?i32 = 234;

    optional_int = null;

    if (optional_int == null) {
        std.debug.print("optional_int is null!", .{});
    } else {
        std.debug.print("optional_int is {}", .{optional_int});
    }
}

null でないことを保証するには orelse を使う。

    const opt_u32: ?u32 = null;

    // opt_u32 が null の場合、 bar は 100 になる
    const bar = opt_u32 orelse 100;

    std.debug.print("{}\n", .{bar});  // 100

Error の catch と似ている。
catch は Error の場合、指定の値にする。
orelse は null の場合、指定の値にする。


docs

046_optionals2.zig

bar.? は、bar orelse unreachable のショートカットで、 null ではないことを示している。
絶対にないことを表すときには、 unrechable を使うのが zig らしい書き方なのかな?

const foo = bar.?;

// 以下と同じ
const foo = bar orelse unreachable;

*Elephant が1つの型だから、その前に ? をつける

const Elephant = struct {
    letter: u8,
    tail: ?*Elephant = null,
    visited: bool = false,
};

047_methods.zig

エイリアン... Land of Lisp みたいw

構造体に関数を紐付けられる。データと手続きをまとめる。
クラスみたい。
Python のように第1引数には self が渡される。

引数によって、メソッドの種類が変わる?

  • 第1引数が自分自身の型ではないなら、クラスメソッド。
  • 第1引数が自分自身の型なら、インスタンスメソッド。
const Alien = struct {
    health: u8,

    pub fn hatch(strength: u8) Alien {
        return Alien{
            .health = strength * 5,
        };
    }

    pub fn zap(self: *Alien, damage: u8) void {
        self.health -= if (damage >= self.health) self.health else damage;
    }
};

pub fn main() void {
    var alien = Alien.hatch(10);

    alien.zap(3);

    std.debug.print("alien health is {}", .{alien.health});
}

048_methods2.zig

メソッドをいくつか書いた。

049_quiz6.zig

トランク (trunk, 胴体, 幹、、、)を実装?

メソッドの実装。

050_no_value.zig

zig では、値がないことを表す方法が4つもある

  • undefined: まだ値がない。これはまだ読み取れない。
  • null: 値がないという明示的な値。
  • error: なにか問題が発生したため、値がない。
  • void: ここに値が保存されることはない。

undefined

var foo: u8 = undefined;

値として考えない。
値がまだ割り当てられていないということをコンパイラに伝えるために使う。
値を読み取ったり、使ったりしようとするのはNG

null

var foo: ?u8 = null;

値がない (no value) ことを表す値。
通常、?u8 のようにオプション型で使われる。
var foo: ?u8 = null とした場合、 foo には u8 型の値が入っていないことを表す。

error

var foo: MyError!u8 = error;

error は null とよく似ている。
null と error は値だけど、探していた実際の値 (real value) が存在しないことを表している。その代わりに error が発生する。
MyError!u8 の error union type の例は、foo が u8 の値かエラーのいずれかを保持していることを表している。
もし、 error がセットされている場合、foo には u8 の値はセットされていない。

void

var foo: void = {};

void は値ではなく、型。
void をコンパイルすると、コードを何も生成しない。 (実行ファイルのサイズを削減できる)
関数定義で何も返さないことを表すときに、 void を使う。

051_values.zig

// @import() はインポートしたコードをそのコードに追加する。
// 以下のようにした場合、 std という構造体に保存され、そこから使用できる。
const std = @import("std");

// 構造体は、メモリ処理に適している。
// 各フィールドはすべて型が指定されていて、サイズがわかる。
// そのサイズを合計すると、その構造体のサイズになる。
// この構造体のサイズは `u32 + u8 + u32`
const Character = struct {
    gold: u32 = 0,
    health: u8 = 100,
    experience: u32 = 0,
};

// 以下のように構造体のインスタンスを作成すると、
// データとしてプログラムに保存されて、命令コードと同様にプログラムの実行時に RAM にロードされる。
// また、メモリ内でのこのデータの相対的な位置は (命令コードに?) ハードコーディングされていて、アドレスも値も変更されない。
// const だから、フィールドも変わらないからハードコーディングできる
const the_narrator = Character{
    .gold = 12,
    .health = 99,
    .experience = 9000,
};

// もし、 var で宣言されていた場合、そのデータのアドレスの場所は変わらないけど、データ自体は変わることがある。 (var だから)
var global_wizard = Character{};

// 関数は特定のアドレスの命令コードになる。
// zig の関数のパラメータは const (不変)。
// パラメータはスタックに保存される。
// CPU はスタックへの追加と削除を特別に (?) サポートしているため、メモリストレージとしては最適。
//
// 関数が実行されると、その関数用のフレームが作成される。 (main() も同様)
pub fn main() void {
    // glorp キャラクターが関数のスタックに保存される。
    // var だから、関数の呼び出しごとに変更可能であるため、スタックに入る
    var glorp = Character{
        .gold = 30,
    };

    // skull_farmer キャラクターは関数で定義されているけど、
    // グローバルな不変データに配置される。
    // 不変であるため、すべての関数呼び出しで同じデータにアクセスできるため (?)
    const skull_farmer = Character{};

    // reward_xp は定数であるため、他のグローバルデータと組み合わせられる。
    // コンパイラによっては、命令コードにリテラル値をインライン化するかもしれない。
    const reward_xp: u32 = 200;

    // @import() でインポートした std は普通のzigの構造体となるため、
    // フィールドと宣言に新しい名前をつけられる。
    // std.debug.print をみると、
    //   debug は std のフィールドで、
    //   printは debug で定義されている関数
    //
    // std.debug.print を print に割り当てる
    const print = std.debug.print;

    // zig の値の割り当てとポインタを見てみる
    //
    // glorp キャラクターにアクセスして、1つのフィールドの値を変更する方法を3つ見てみる

    // 1: 新しい構造体にすべてのフィールドの値をコピーする方法
    // 全く同じフィールドの値を持った、新しい構造体ができる
    var glorp_access1: Character = glorp;
    glorp_access1.gold = 111;
    print("1:{}!. ", .{glorp.gold == glorp_access1.gold});

    // 2: ポインタを取得する方法 (ポインタの変数を var で定義)
    // glorp_access2 は元のglorpのアドレスを指している。
    // NOTE: *const Character ではだめ
    var glorp_access2: *Character = &glorp;
    glorp_access2.gold = 222;
    // glorp_access2.gold は暗黙的に逆参照をしている
    // glorp_access2.*.gold = 222;
    print("2:{}!. ", .{glorp.gold == glorp_access2.gold});

    // 3: ポインタを取得する方法 (ポインタの変数を const で定義)
    // 変数が const なだけで、変数が指しているアドレスの値の変更は可能
    const glorp_access3: *Character = &glorp;
    glorp_access3.gold = 333;
    print("3:{}!. ", .{glorp.gold == glorp_access3.gold});

    // NOTE: *const Character 型のポインタにすると、そのポインタが指すアドレスの値は変更できない。
    //
    // 関数に引数を渡すことは、const に値を割り当てることと同じ。
    // パラメータは不変だから、変更したい場合、他の変数に割り当ててから変更しないといけない

    print("XP before:{}, ", .{glorp.experience});

    levelUp(&glorp, reward_xp);

    print("after:{}.\n", .{glorp.experience});
}

fn levelUp(character_access: *Character, xp: u32) void {
    character_access.experience += xp;
}

メモリの領域は、データセグメント (コンパイル時に割り当てられる) と スタック (実行時に割り当てられる) 以外にも、ヒープがある。

ヒープは、動的に確保可能なメモリの領域。

https://ja.wikipedia.org/wiki/ヒープ領域

052_slices.zig

020_quiz3.zig では、配列の個数を指定した関数定義をしていた。
これは使いづらいため、スライスとポインタを使って扱いやすくする。

これは、配列の型がサイズを含んでいるため、起こる問題。
だから、配列を扱うにはサイズをハードコーディングする必要がある。

Zig にはスライス (slices) というものがあるため、配列から動的に指定の範囲の要素を取得できる。

変数[開始..終了] で取得できる (Python でいうと 変数[開始:終了] みたいな)
開始は含んで、終了は含まない

const std = @import("std");

pub fn main() void {
    var s = [_]u8 {'h', 'e', 'l', 'l', 'o'};

    // スライスで受け取る
    const a: []u8 = s[0..2];

    for (a) |val| {
        std.debug.print("{c}", .{val});
    }
}

// he

053_slices2.zig

文字列から、部分文字列を取得するときにもスライスが使える。
文字列は、u8 の配列 ([]u8) だから、使える。

zig の場合、文字列リテラルは const となっているため、以下のようにする必要がある。

const std = @import("std");

pub fn main() void {
    // 文字列リテラルは不変
    var s = "hello";

    var sub_s: []const u8 = s[0..2];

    std.debug.print("{s}\n", .{sub_s});    // he
}
作成者以外のコメントは許可されていません