Zig 昨日触りはじめたばかりでまだぜんぜん分かってません。
まずは std.os.argv
でコマンドライン引数を取得します。
pkv
は確認用のユーティリティ関数で、 print key value のつもり。
// arg_to_i_v1.zig const std = @import("std"); fn pkv(k: []const u8, v: var) !void { std.debug.warn("{} ({})\n", .{k, v}); } pub fn main() !void { try pkv("std.os.argv", std.os.argv); try pkv("std.os.argv type", @TypeOf(std.os.argv) == [][*:0]u8); const arg1: [*:0]u8 = std.os.argv[1]; try pkv("arg1", arg1); }
$ zig-0.6.0 run arg_to_i_v1.zig -- 42 std.os.argv ([*:0]u8@7ffc55805dd8) std.os.argv type (true) arg1 (42)
コマンドライン引数が文字列(というかバイト列)の配列として取得できたので、次はこれを i32 に変換します。
標準ライブラリに std.fmt.parseInt
という関数があったので適当に使ってみました。
// arg_to_i_v2.zig const std = @import("std"); pub fn main() !void { const arg1: [*:0]u8 = std.os.argv[1]; const n: i32 = try std.fmt.parseInt( i32, // 変換後の型 arg1, // 変換元の文字列 10 // 基数 ); }
これは型が違うためエラーになります。
./arg_to_i_v2.zig:14:5: error: expected type '[]const u8', found '[*:0]u8' arg1, // 変換元の文字列
ここで詰まったので、一度落ち着いて、型について見てみることにしました。
std.os.argv
の型は [][*:0]u8
、
その要素(それぞれの引数)の型は [*:0]u8
。
[]Foo
は Foo のスライス。
この表記はスライスだ、と丸呑みしてもよいですが、
これが出てきたら []
の中が空なのでコンパイル時には要素数(長さ)が決まらない(実行時に決まる)
……みたいなことを考えればいいんでしょうか。
[*]Foo
は Foo の配列へのポインタ型。
うーん、この表記は覚えるしかなさそう。
Cの配列のように先頭と要素の型に関する情報は持っているが、要素数の情報は持たない。
[A:B]Foo
は要素数が A で終端の要素(sentinel)が B である Foo の配列を表す型。
たとえば、[4:0]u8
は要素数が 4 で、終端の要素が 0 である u8 の配列の型、となる。
[ 11, 22, 33, 44, 0 ] ^ ここ(xs[要素数])が 0
で、 [*:0]Foo
は何なのか。
要素数が *
とは??
と思ってしまいましたが、
これは「終端が0である配列」へのポインタ型、と読めばいいようです。たぶん。
終端が 0 であることは分かっているが要素数は分からない配列の先頭を差すポインタの型。
[len:sentinel]
のパターンに当てはめるのではなく、この場合は
[*]Foo
に終端の情報 :0
が加わっている、と捉えればよいのでしょうか。
あと残っているのは []const u8
の const
ですね。
const
を外して []u8
だけだったら何かというと、えーとこれは「u8
のスライス」ですね。
ただの []Foo
だと要素を書き換えられるスライスですが、
[]const Foo
だと要素を書き換えられないスライスになるようです。
var xs: [3]u8 = [3]u8{ 'a', 'b', 'c' }; const slice: []u8 = xs[0..xs.len]; slice[0] = 'A'; const unmodifiable_slice: []const u8 = xs[0..xs.len]; unmodifiable_slice[0] = 'A'; //=> error: cannot assign to constant
std.fmt.parseInt
が []const u8
を要求しているということは、つまり
「std.fmt.parseInt
では渡されたスライスの内容を変更しません(できません)よ」
ということを引数の型で表明しているのだと思います。
std.fmt.parseInt
を使う側は「呼び出した関数の中で引数で渡したものの内容が書き換えられたりしないか?」という心配をしなくて済む、ありがたい、ということでしょう。
他の例としては、たとえば OutStream の write 関数や print 関数に渡す文字列の型も []const u8
になっています。
https://github.com/ziglang/zig/blob/0.6.0/lib/std/io/out_stream.zig
pub fn write(self: Self, bytes: []const u8) Error!usize { ... } pub fn print(self: Self, comptime format: []const u8, args: var) Error!void { ... }
ここまで分かったので、次にすべきなのは [*:0]u8
から []const u8
への変換です。
// こういう形に持っていきたい: const slice: []const u8 = ??? // [*:0]u8 から変換する const n: i32 = try std.fmt.parseInt(i32, slice, 10);
枝葉を無視して「配列からスライスへの変換」と考えてみます。
配列からスライスを作る基本的な方法はこう。
const xs: [3]u8 = [3]u8{ 'a', 'b', 'c' }; const slice: []u8 = xs[0..xs.len];
ですが、今必要なのは [*:0]u8
から []const u8
への変換なので、
その形に持って行きたい。
変換後は []const u8
にしたいので、とりあえず const
を付けてみます。
const xs: [3]u8 = [3]u8{ 'a', 'b', 'c' }; const slice: []const u8 = xs[0..xs.len]; // ^^^^^
えーとそれから次は……この例だと変換元が [3]u8
と要素数を指定していますが、これを [*:0]u8
にしたいので…… :0
を付けてみましょうか。
const xs: [3:0]u8 = [3:0]u8{ 'a', 'b', 'c' }; const slice: []const u8 = xs[0..xs.len];
ここから [*:0]u8
にすると、スライスに変換するときに必要な xs.len
が取れなくなります。
const xs: [*:0]u8 = ...; const slice: []const u8 = xs[0..???];
[*:0]u8
ということは、要素数は分からないけれど終端が 0 であることは分かっています。
なので、要素数を知りたければ 0 の位置を探せばいいんじゃないでしょうか。
次のような関数を書いてみました。
fn countChars(chars: [*:0]u8) usize { var i: usize = 0; while (true) { if (chars[i] == 0) { return i; } i += 1; } }
これを使って、 [*:0]u8
な配列を作って []const u8
に変換するサンプルコードを書いてみました。
// arg_to_i_v3.zig // ... snip ... pub fn main() !void { var xs: [5:0]u8 = [5:0]u8{ 'a', 'b', 'c', 0, 'd' }; try pkv("xs", xs); try pkv("xs.len", xs.len); const xs2: [*:0]u8 = &xs; try pkv("xs2", xs2); // try pkv("xs2.len", xs2.len); // => error: type '[*:0]u8' does not support field access const slice = xs2[0..countChars(xs2)]; try pkv("slice", slice); try pkv("slice.len", slice.len); }
$ zig-0.6.0 run arg_to_i_v3.zig xs (abcd) xs.len (5) xs2 (abc) slice (abc) slice.len (3)
できました。
ためしに途中の要素 xs[3]
を 0 にしてみたため abc
までで打ち切られていますが、
終端が 0 だということになっているのでこれはこれでよいでしょう。
ここまでできたらあとは parseInt に渡すだけです。 まとめると下記のようになりました。
// arg_to_i_v4.zig const std = @import("std"); fn pkv(k: []const u8, v: var) !void { std.debug.warn("{} ({})\n", .{k, v}); } fn countChars(chars: [*:0]u8) usize { var i: usize = 0; while (true) { if (chars[i] == 0) { return i; } i += 1; } } pub fn main() !void { const arg1: [*:0]u8 = std.os.argv[1]; const arg1_unmodifiable_slice: []const u8 = arg1[0..countChars(arg1)]; const n: i32 = try std.fmt.parseInt( i32, arg1_unmodifiable_slice, 10 ); try pkv("n", n); }
$ zig-0.6.0 run arg_to_i_v4.zig -- 42 n (42)
上記の countChars
に相当する関数は標準ライブラリにあるんじゃないかと思って調べてみましたが、ちょっと分かりませんでした。
が、テストコードの中に strlen
という関数があるのは見つけました。
https://github.com/ziglang/zig/blob/0.6.0/test/stage1/behavior/const_slice_child.zig#L35
fn strlen(ptr: [*]const u8) usize { var count: usize = 0; while (ptr[count] != 0) : (count += 1) {} return count; }
だいたい同じことやってますね。
参考
- (2019-07-23) オープンソースプログラミング言語zigまとめ - Qiita
- たいへん参考になりました
- v0.4.0 時点