Zig 言語
関数
- 関数の引数はイミュータブル(値を変更できない)
- 関数名はキャメルケース(e.g. myFunction)
Defer
test "defer" {
var x: i16 = 5;
{
defer x += 2;
try expect(x == 5); // pass
}
try expect(x == 7); // pass
}
defer
は現在のブロック({}
で囲まれてる部分)を抜けるときに実行される
リソースの開放をするときとかに便利そう
Error
Zigに例外はない。エラーは値である
const FielOpenError = error{
AccessDenied,
OutofMemory,
FileNotFound,
};
列挙型に似ている(らしい)
const AllocationError = error{OutOfMemory};
test "error union" {
const maybe_error: AllocationError!u16 = 10; // error set と u16 のunion型
const no_error = maybe_error catch 0;
try expect(@TypeOf(no_error) == u16); // pass
try expect(no_error == 10); // pass
}
catch
以降の式はその前の式がエラーのとき評価される
test "error test" {
const maybe_error: AllocationError!u16 = AllocationError.OutOfMemory;
const no_error = maybe_error catch (1 + 5);
std.debug.print("\ntype of no_error = {!}\n", .{@TypeOf(no_error)});
try expect(no_error == 6); // pass
}
type of no_error = comptime_int
エラーのマージ
const A = error{ NotDir, PathNotFound };
const B = error{ OutOfMemory, PathNotFound };
const C = A || B;
std.debug.print("\nC = {!}\n", .{C});
C = error{OutOfMemory,NotDir,PathNotFound}
これもいけた
test "big exp after catch" {
var i: i32 = 1;
const maybe_error: AllocationError!u16 = AllocationError.OutOfMemory;
_ = maybe_error catch {
while (true) {
if (i == 1024) break;
i *= 2;
}
try expect(i == 1024);
};
}
switch
は文にも式にもなる
test "switch statement" {
var x: i8 = 10;
switch (x) {
-1...1 => {
x = -x;
},
10, 100 => {
x = @divExact(x, 10);
},
else => {},
}
try expect(x == 1);
x = switch (x) {
-1...1 => -x,
10, 100 => @divExact(x, 10),
else => x,
};
try expect(x == -1);
}
Runtime Safety
安全ではないコードを書ける
test "@setRuntimeSafety" {
{
var x: u8 = 255;
var y: i8 = 127;
{
@setRuntimeSafety(false);
x += 1;
y += 127;
y += 2;
std.debug.print("\nx = {x} y = {x}\n", .{ x, y });
}
}
}
x = 0 y = 0
ポインタ
基本的にはc言語と同じ。
参照外しはhoge.*
fn increment(num: *u8) void {
num.* += 1;
}
test "pointers" {
var x: u8 = 1;
increment(&x);
try expect(x == 2);
}
定数へのポインタは参照外しを行っても値の変更はできない
配列とポインタ
fn increment(arr_ptr: [*]u8) void {
arr_ptr[0] += 1;
}
test "pointers" {
var y: [5]u8 = [5]u8{ 1, 2, 3, 4, 5 };
increment(&y);
try expect(y[0] == 2);
}
Enum
const Direction = enum { north, south, east, west };
const Value = enum(u2) { zero, one, three = 3 };
test "enum type" {
try expect(@TypeOf(Direction.north) == Direction);
}
test "enum ordinal value" {
try expect(@intFromEnum(Value.zero) == 0);
try expect(@intFromEnum(Value.one) == 1);
try expect(@intFromEnum(Value.three) == 3);
}
メソッドの定義も可能
const Suit = enum {
var count: u32 = 0;
clubs,
spades,
diamonds,
hearts,
pub fn isClubs(self: Suit) bool {
return self == Suit.clubs;
}
};
test "enum method" {
try expect(Suit.spades.isClubs() == Suit.isClubs(.spades));
Suit.count += 1;
try expect(Suit.count == 1);
}
構造体 struct
やりたい放題
const Vec3 = struct {
x: f32,
y: f32,
z: f32 = 0,
fn swap(self: *Vec3) void {
const tmp = self.x;
self.x = self.y;
self.y = tmp;
}
};
test "struct usage" {
var my_vector = Vec3{
.x = 0,
.y = 100,
};
my_vector.swap();
try expect(my_vector.x == 100);
try expect(my_vector.y == 0);
}
swap()
関数を見てわかるとおり、struct
へのポインタは勝手に参照外しされる。
union
enumはunionのどのフィールドがアクティブか調べるのに使える
const Result = union {
int: i64,
float: f64,
bool: bool,
};
test "simple union" {
var result = Result{ .int = 1234 };
result.float = 12.34; // アクティブでないフィールドにはアクセスできない
}
const Tag = enum { a, b, c };
const Tagged = union(Tag) { a: u8, b: f32, c: bool };
test "switch on tagged union" {
var value = Tagged{ .b = 1.5 };
switch (value) {
.a => |*byte| byte.* += 1,
.b => |*float| float.* *= 2,
.c => |*b| b.* = !b.*,
}
try expect(value.b == 3);
}
下と上は同じ
const NewTagged = union(enum) { a: u8, b: f32, c: bool };
test "union with enum" {
var value = NewTagged{ .b = 1.5 };
switch (value) {
.a => |*byte| byte.* += 1,
.b => |*float| float.* *= 2,
.c => |*b| b.* = !b.*,
}
try expect(value.b == 3);
}
ラベル付きブロック
ブロックにはラベルをつけることができ、値と同じように使える
test "labelled blocks" {
const count = blk: {
var sum: u32 = 0;
var i: u32 = 0;
while (i < 10) : (i += 1) sum += i;
break :blk sum;
};
try expect(count == 45);
try expect(@TypeOf(count) == u32);
}
ラベル付きループ
ループに名前をつけることで、特定のループをcontinue
もしくはbreak
することができる
test "nested continue" {
var count: usize = 0;
outer: for ([_]i32{ 1, 2, 3, 4, 5, 6, 7, 8 }) |_| {
for ([_]i32{ 1, 2, 3, 4, 5 }) |_| {
count += 1;
continue :outer;
}
}
try expect(count == 8);
}
comptime
comptime
をブロックの前につけるとコンパイル時に実行されるようになるそう
test "comptime blocks" {
const x = comptime fibonacchi(10);
const y = comptime blk: {
break :blk fibonacchi(10);
};
try expect(y == 55);
try expect(x == 55);
}
フィールド名のない構造体はタプルになる。タプルの要素は@""
でアクセスできる
test "tuple" {
const values = .{
@as(u32, 1234),
@as(f64, 12.34),
true,
"hi",
} ++ .{false} ** 2 ++ .{.{false}};
try expect(values[0] == 1234);
try expect(values[4] == false);
inline for (values, 0..) |v, i| {
if (i != 2) continue;
try expect(v);
}
try expect(values.len == 7);
try expect(values.@"3"[0] == 'h');
debug.print("\n{!}\n", .{values});
}
{ 1234, 1.234e1, true, { 104, 105 }, false, false, { false } }
vector
vectorは配列のようにインデックスでアクセスが可能
test "vector add" {
const x: @Vector(4, f32) = .{ 1, -10, 20, -1 };
const y: @Vector(4, f32) = .{ 2, 10, 0, 1 };
const z = x + y;
try expect(z[1] == 0);
}
標準ライブラリ
Allocators
標準ライブラリでは意図しないメモリのアロケーションは起こり得ない
基本的なアロケーションの方法は以下の通り。
(後述する方法より非効率)
const std = @import("std");
const expect = std.testing.expect;
test "allocation" {
const allocator = std.heap.page_allocator;
const memory = try allocator.alloc(u8, 100);
defer allocator.free(memory);
try expect(memory.len == 100);
try expect(@TypeOf(memory) == []u8);
}
以下の方法では固定されたバッファからメモリをアロケートするため、heapからのアロケーションは起こらないという。
test "fixed buffer allocation" {
var buffer: [1000]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const allocator = fba.allocator();
const memory = try allocator.alloc(u8, 100);
defer allocator.free(memory);
try expect(memory.len == 100);
try expect(@TypeOf(memory) == []u8);
}
単独の値の場合以下のようにアロケートする。
test "allocator create/destroy" {
const byte = try std.heap.page_allocator.create(u8);
defer std.heap.page_allocator.destroy(byte);
byte.* = 128;
}
general purpose allocator
略してGPA
パフォーマンスより安全性を優先しており、double-free
やuaf
を防ぐ
test "gpa" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
defer {
const deinit_status = gpa.deinit();
if (deinit_status == .leak) expect(false) catch @panic("TEST FAIL");
}
const bytes = try allocator.alloc(u8, 100);
defer allocator.free(bytes);
}
パフォーマンスを重視するならstd.heap.c_allocator
も使える。コンパイル時に-lc
でlibc
とリンクすることで利用可能。
ArrayList
std.ArrayLIst
はc++のstd::vector<T>
と似ている
使用後は.deinit()
を忘れずに
const ArrayList = std.ArrayList;
const test_allocator = std.testing.allocator;
test "arraylist" {
var list = ArrayList(u8).init(test_allocator);
defer list.deinit();
try list.append('H');
try list.append('e');
try list.append('l');
try list.append('l');
try list.append('o');
try list.appendSlice(" World!");
try expect(eql(u8, list.items, "Hello World!"));
try expect(list.items[0] == 'H');
}
FileSystem
ファイルの作成
test "createFile, write, seekTo, read" {
const file = try std.fs.cwd().createFile(
"jnk_file.txt",
.{ .read = true },
);
defer file.close();
const bytes_written = try file.writeAll("hello file!");
_ = bytes_written;
var buffer: [100]u8 = undefined;
try file.seekTo(0);
const bytes_read = try file.readAll(&buffer);
std.debug.print("\n{!}\n", .{file.stat()});
try expect(eql(u8, buffer[0..bytes_read], "hello file!"));
}
fs.File.Stat{ .inode = 23484707, .size = 11, .mode = 33188, .kind = fs.File.Kind.file, .atime = 1714909232899397231, .mtime = 1714909232899397231, .ctime = 1714909232899397231 }
ディレクトリの作成
test "create dir" {
try std.fs.cwd().makeDir("junk-dir");
var junk_dir = try std.fs.cwd().openDir(
"junk-dir",
.{},
);
defer {
junk_dir.close();
std.fs.cwd().deleteTree("junk-dir") catch unreachable;
}
std.debug.print("\n{!}\n", .{junk_dir.stat()});
}
fs.File.Stat{ .inode = 23484705, .size = 4096, .mode = 16877, .kind = fs.File.Kind.directory, .atime = 1714909232899397231, .mtime = 1714909232899397231, .ctime = 1714909232899397231 }
Reader
readAllAlloc()
は第二引数未満の大きさのストリームを全て取り込むためのメモリをアロケートしてくれる。
test "io read usage" {
const message = "Hello World!";
const file = try std.fs.cwd().createFile(
"junk_file.txt",
.{ .read = true },
);
defer file.close();
try file.writeAll(message);
try file.seekTo(0);
const contents = try file.reader().readAllAlloc(
test_allocator,
message.len,
);
defer test_allocator.free(contents);
try expect(eql(u8, contents, message));
}
乱数 random
test "random numbers" {
var prng = std.rand.DefaultPrng.init(blk: {
var seed: u64 = undefined;
try std.posix.getrandom(std.mem.asBytes(&seed));
break :blk seed;
});
const rand = prng.random();
const a = rand.float(f32);
const b = rand.boolean();
std.debug.print("\n{} {}\n", .{ a, b });
}
9.582089e-1 false
Build
zigにはbuild
というコマンドがある。
恐らく、cとかc++でいうmakeにあたるもので、ビルドの設定をbuild.zig
(makefile
にあたるもの?)に記述する
const std = @import("std");
pub fn build(b: *std.Build) void {
const exe_name = b.option(
[]const u8,
"exe_name",
"Name of the executable",
) orelse "hello";
const exe = b.addExecutable(.{
.name = exe_name,
.root_source_file = .{ .path = "src/main.zig" },
.target = b.standardTargetOptions(.{}),
.optimize = b.standardOptimizeOption(.{}),
});
b.installArtifact(exe);
}
const std = @import("std");
pub fn main() void {
std.debug.print("Hello, {s}!\n", .{"Zig Build"});
}
$ zig build --summary all
Build Summary: 3/3 steps succeeded
install cached
└─ install hello cached
└─ zig build-exe hello Debug native cached 2ms MaxRSS:37M
zig から c を呼ぶ
network
c言語みたいにソケットから作って色々すると思っていたので、下のようになるまで時間がかかった