Open4

ChromebookでZigを始める

chimamechimame

Zigのインストール方法を調べる

https://ziglang.org/learn/getting-started/

方法は以下の3つ

  • 自分でビルドする
  • バイナリをダウンロードしてパスを通す
  • パッケージマネージャを使ってインストールする

一番楽そうなパッケージマネージャを使ってみる。

chimamechimame

Zigをインストールする

https://github.com/ziglang/zig/wiki/Install-Zig-from-a-Package-Manager

Ubuntu系は snap を使ってインストールするらしい。とりあえずコンテナの中で動作する環境を目指す。

 $ docker run --rm -it debian:bullseye-slim bash

https://snapcraft.io/docs/installing-snap-on-debian

軽いノリでsnapインストールしようとしたら嫌な記述を見る

Either log out and back in again, or restart your system, to ensure snap’s paths are updated correctly.

ログアウト?再起動?
要は何かsystemd的なプロセス起動が必要ってことを認識する。それはコンテナでは無理ゲーでは?

$ apt update && apt install snapd
$ which snap
# /usr/bin/snap
$ snap install core
# error: cannot communicate with server: Post "http://localhost/v2/snaps/core": dial unix /run/snapd.socket: connect: no such file or directory

やっぱり動かない。
てことで早々に諦めてバイナリダウンロード手法に切り替える。

$ apt update && apt install -y curl xz-utils
$ curl -OL https://ziglang.org/download/0.9.1/zig-linux-x86_64-0.9.1.tar.xz
$ tar Jxf ./zig-linux-x86_64-0.9.1.tar.xz -C /usr/local/bin
$ rm -rf ./zig-linux-x86_64-0.9.1.tar.xz
$ export PATH=$PATH:/usr/local/bin/zig-linux-x86_64-0.9.1
$ zig version
# 0.9.1

サクッとインストール完了

最終的に出来上がったDockerfile

Dockerfile
FROM debian:bullseye-slim

ARG ZIG_VERSION=0.9.1
RUN apt update && apt install -y curl xz-utils
RUN curl -OL https://ziglang.org/download/0.9.1/zig-linux-x86_64-${ZIG_VERSION}.tar.xz && \
  tar Jxf ./zig-linux-x86_64-${ZIG_VERSION}.tar.xz -C /usr/local/bin && \
  rm -rf ./zig-linux-x86_64-${ZIG_VERSION}.tar.xz
ENV PATH $PATH:/usr/local/bin/zig-linux-x86_64-${ZIG_VERSION}
chimamechimame

Hello Worldの出力

環境が整ったので、恒例のHello Worldを出力する。
まずは作った Dockerfile から動作環境用のコンテナイメージを作成&起動

$ docker build -t zig:0.9.1 .
$ docker run --rm -it -v $(pwd):/app -w /app zig:0.9.1 bash

環境に入ったらZigの初期セットアップコマンドを実行

$ zig init-exe

出力されるプログラムは

src/main.zig
const std = @import("std");

pub fn main() anyerror!void {
    std.log.info("All your codebase are belong to us.", .{});
}

test "basic test" {
    try std.testing.expectEqual(10, 3 + 7);
}

解説は後でやります。(Zigはインデント4とセミコロンの世界)
実行するには

$ zig run src/main.zig
# info: All your codebase are belong to us.

これでビルドと実行が同時に実施される。ビルドだけしたい場合は

$ zig build
$ ./zig-out/bin/app
# info: All your codebase are belong to us.

という感じでビルドとバイナリを実行するということになる。
表題通りにの「Hello World」を出力するには以下のように書き換える。

src/main.zig
const std = @import("std");

pub fn main() anyerror!void {
-    std.log.info("All your codebase are belong to us.", .{});
+    std.log.info("Hello {s}", .{"World"});
}

test "basic test" {
    try std.testing.expectEqual(10, 3 + 7);
}

同じように実行すると

$ zig run src/main.zig
# info: Hello World

となる。

chimamechimame

Zigの文法

Zigの文法をチェック

コメント

Zigのコメントは // スラッシュ2つで行単位をコメントにできる。しかし他の言語と違って /* ~ */ のような複数行のコメントは存在しない。

// ここはコメントになる
std.log.info("Hello {s}", .{"World"}); // 行末につけることも可能

変数

宣言

//const or var <variable name>: <type> = <input data>
const age: i32 = 18;

const もしくは var で変数名を宣言し、後に型を記述する方式。型の種類についてはドキュメントを参照。
constvar の違いはJavaScript同様に const は再代入が行えず、 var は行えるというもの。

const age = 30;
age = 18; // error

var age = 30;
age = 18; //ok

変数を宣言する場合は初期値の設定が必ず必要になる。

const age: i32; // error
const age: i32 = undefined;

もし、初期値がない場合は undefined を割り当てて必要がある。

配列

const message = [_]u8{'H', 'e', 'l', 'l', 'o'};
std.log.info("{s} {s}", .{&message,"World"});

配列はちょっと変わった宣言方法。まず最初の [_] これで「任意の数の配列」というのを表している。仮に個数が決まっているなら [5] という風に5個の配列と宣言することになる。続いて配列に格納する型を指定する。今回は文字列型の1つの u8 を指定している。最後に配列に入れる要素を {} 内に , 区切りで宣言する。例は Hello という文字を1文字1文字の配列として宣言している。出力するのに配列ままでは出力できないので、 & で結合している。

if

const age = 18;
if (age == 18) {
    // trueの場合の処理
    std.log.info("age is 18");
} else {
    // falseの場合の処理
    std.log.info("age is not 18");
}

そんなに他の言語と変わらないif文。条件式内の返り値は true false null の3つしか許されていない。他の言語のような 0 1 などの整数やオブジェクト自体が存在するというような条件では使用できない。
nullfalse と同じ扱いになる。
ちょっと変わった?使い方としては3項演算子的に使いつつRubyのif文と同じような返り値を受け取るみたいな動きになる構文がある

const age: i32 = 18;
const message = if (age == 18)
    "age is 18"
else
    "age is not 18";
std.log.info("output: {s}", .{message});
// info: output: age is 18

こう書くのは ; が大事。結局はどこまでを1つ命令とするかというのがこのセミコロンによって解釈させないとダメということ。

for

const message = [_]u8{ 'H', 'e', 'l', 'l', 'o' };
for (message) |char| {
    std.log.info("output: {c}", .{char});
}

//info: output: H
//info: output: e
//info: output: l
//info: output: l
//info: output: o

少し変わった感じのfor文。配列を渡すとイテレータとして処理して1つ1つの要素をブロックで受ける感じの構文です。ちょっとRubyを感じる。インデックスを受け取るには配列要素取得の第2引数(ちょっと呼び方わからない)に指定した変数で取得できる。

const message = [_]u8{ 'H', 'e', 'l', 'l', 'o' };
for (message) |char, index| {
    std.log.info("output: {d} {c}", .{index, char});
}

//info: output: 0 H
//info: output: 1 e
//info: output: 2 l
//info: output: 3 l
//info: output: 4 o

添字は0から始まる。

while

プログラムの基本は分岐と繰り返しなのでwhileまであれば大体の処理は書けるということになる。

var i: usize = 0;
while (i < 10) {
    i += 1;
}

普通にただの癖のないwhile文。これじゃ面白くないなと思ってみてると以下のような構文が存在する。

var i: usize = 1;
var j: usize = 1;
while (i * j < 2000) : ({ i *= 2; j *= 3; }) {
   const ij = i * j;
    std.log.info("output: {d} {d} {d}", .{i, j, ij});
}

({ i *= 2; j *= 3; }) の部分。パッと見たイメージ通りの動きで、whileのループ1回終われば ({ i *= 2; j *= 3; }) が評価される。i は2倍、 j は3倍で数が増えていくというルールの動き。増加値を別に定義して書けるのはわかりやすい。というか従来のfor文に似てる。