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!
お!できた
手順としては、
- zig build 番号
- 編集
- zig build 番号
で進めていく
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
を使って宣言する
for で *n
のようにしていたのは、なんで?
for (leet) |*n| {
std.debug.print("{}", .{n.*});
}
n
でもループできるのに...
for (leet) |n| {
std.debug.print("{}", .{n});
}
文字列リテラルに対しても、配列演算子の **
と ++
が使える
-
**
: 文字列の繰り返し -
++
: 文字列の連結
std.debug.print()
の {}
に値の情報をかける。
-
{u}
: UTF-8 character -
{s}
: UTF-8 strings
もし、{}
だけにすると、10進数として表示しようとする。そのため、文字列を渡すとエラーになる。
(std.debug.print("d={u} {s}{}\n", .{ d, laugh, major_tom });
としたら、エラーになった)
008_quiz.zig
undefined
でなんでも無い値として変数を初期化して、宣言できる。
zig の docs を見てみる
undefined を使うことで、変数を初期化しないで宣言できる。
undefined は、値が何でもあり得ることを示すために使う。
使う前に初期化されるであろうな値らしい。
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", .{});
}
複数の式を評価できる。
while (条件式) : ({ 式1; 式2; ... ;}) { ... }
var i: u8 = 0;
while (i < 10) : ({
i += 1;
i += 1;
}) {
std.debug.print("{}\n", .{i});
}
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;
}
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~
std.debug.print()
は {}
とすると数値を出力しようとする。
文字を表示したい場合、{c}
を使用し、
文字列を表示したい場合、{s}
を使用する。
// o は8進数
std.debug.print("{o}", .{9}); // 11
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, .{});
^
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
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;
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 の場合、指定の値にする。
*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});
}
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;
}
メモリの領域は、データセグメント (コンパイル時に割り当てられる) と スタック (実行時に割り当てられる) 以外にも、ヒープがある。
ヒープは、動的に確保可能なメモリの領域。
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