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 番号
で進めていく
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
の意味について
u8
や i8
の u と i はそれぞれ、 「u
は unsigned
(符号なし)」と、「i
は符号あり」の意味。
また、u8
の 8
はビット数となっていて、例えば、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' };
007_strings2.zig
先頭に \\
を書くことで複数行の文字列が記述できる
\\ hello
とかくと、先頭にスペースが入った文字列 (" hello"
) になるため、注意が必要
(tree-sitter がバグっているけど、わからなかった...)
008_quiz.zig
undefined
でなんでも無い値として変数を初期化して、宣言できる。
zig の docs を見てみる
undefined を使うことで、変数を初期化しないで宣言できる。
undefined は、値が何でもあり得ることを示すために使う。
使う前に初期化されるであろうな値らしい。
009_if.zig
zig の if
は真偽値 のみ 受け取る
いいなこれ。
だから、1 が true
とか null
が false
とかはならないってこと。
明示的に比較する。
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", .{});
}
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
for
と while
を使う。
あと関数の型も
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
を使って、そのスコープの最後に処理を実行できる
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
これ、いつ使うんだろう...?
他の人のコードを読まないと!!
028_defer2.zig
面白い
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 は、そこへは到達しないことを表明するためにも使える (らしい)
もし、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| {
// エラーのとき
}
else
と switch
を組み合わせてかけるの面白い
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,
};
038_structs2.zig
構造体は1つの型だから、配列の要素としても扱える。
039_pointers.zig
ポインタ:値への参照
*u8
:u8
の値へのポインタ型
&foo
:foo
への参照
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 の場合、指定の値にする。
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});
}
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
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
}