Zig で Welcome to AtCoder を解いてみた

[Qiita Engineer Festa 2024（キータ・エンジニア・フェスタ 2024） - Qiita](https://qiita.com/official-campaigns/engineer-festa/2024)
において、約1ヶ月で38記事という大量の記事の投稿を要求されることがわかった。
そこで、あまりコストをかけずに記事数を稼ぐ方法を考えた結果、「Welcome to AtCoder を様々な言語で解く」ことを思いついた。
単に解くだけでなく、使用する言語仕様の解説を入れれば、記事として一応成立するだろう。

# Welcome to AtCoder

[PracticeA - Welcome to AtCoder](https://atcoder.jp/contests/abs/tasks/practice_1)

Welcome to AtCoder では、以下の形式で整数 $a$, $b$, $c$ および文字列 $s$ が入力として与えられる。

```text
a
b c
s
```

この入力をもとに、与えられた整数の和 $sum = a + b + c$ および文字列 $s$ を、以下の形式で出力することが求められる。

```text
sum s
```

整数 $a$, $b$, $c$ は 1 以上 1,000 以下である。

# 今回用いる Zig の機能

[Documentation - The Zig Programming Language](https://ziglang.org/documentation/0.13.0/)

## 関数定義

[Hello World](https://ziglang.org/documentation/0.13.0/#Hello-World)
[Functions](https://ziglang.org/documentation/0.13.0/#Functions)

```
fn 関数名(引数リスト) 返り値の型 {
    処理内容
}
```

の形式で関数を定義できる。

```
return 値;
```

により、関数から値を返すことができる。

プログラムの実行開始時に実行される関数は

```
pub fn main() !void {
    処理内容
}
```

の形で定義できる。

## 型

[Primitive Types](https://ziglang.org/documentation/0.13.0/#Primitive-Types)

今回は以下の型を用いる。

|型|意味|
|---|---|
|`i32`|32ビット符号つき整数|
|`u32`|32ビット符号なし整数|
|`u8`|8ビット符号なし整数|

## リテラル

[Integer Literals](https://ziglang.org/documentation/0.13.0/#Integer-Literals)
[String Literals and Unicode Code Point Literals](https://ziglang.org/documentation/0.13.0/#String-Literals-and-Unicode-Code-Point-Literals)

整数のリテラルは、十進数 `72` や十六進数 `0x123` などを用いることができる。
文字列リテラルは、`"hello"` のように `"` で囲むことで表現できる。
文字 (文字コード) リテラルは、`'a'` のように `'` で囲む。

## 条件分岐

[if](https://ziglang.org/documentation/0.13.0/#if)

```
if (条件式) {
    条件式が真の場合の処理
} else {
    条件式が偽の場合の処理
}
```

```
if (条件式) {
    条件式が真の場合の処理
}
```

の形式で、条件分岐ができる。

## ループ

[while](https://ziglang.org/documentation/0.13.0/#while)

```
while (true) {
    処理
}
```

の形式で、「処理」を無限に繰り返し実行できる。

```
break;
```

により、このループから抜けることができる。

:::note info
Zig では条件を指定してのループや配列の走査 (for) も使えるが、今回は使用しなかった。
:::

## 定数と変数

[Assignment](https://ziglang.org/documentation/0.13.0/#Assignment)

```
const 識別子 = 値;
```

の形式で、識別子に値を割り当てることができる。(すなわち、定数を定義できる)

```
var 識別子: 型 = 初期値;
```

の形式で、変数を定義できる。
変数には以下のように `=` を用いて値を代入できる。

```
識別子 = 値;
```

:::note info
定数の定義では型を省略しやすいが、変数の定義では型を明示したほうがよさそうである。
たとえば、以下のように型を省略して整数の変数を定義しようとすると、以下のコンパイルエラーになった。

```zig
var value = 0;
```

```
error: variable of type 'comptime_int' must be const or comptime
    var value = 0;
        ^~~~~
note: to modify this variable at runtime, it must be given an explicit fixed-size number type
```
:::

## 演算子

[Operators](https://ziglang.org/documentation/0.13.0/#Operators)

今回は、以下の演算子を用いた。

|演算子|意味|
|:-:|---|
|`a + b`|加算|
|`a - b`|減算|
|`a * b`|乗算|
|`a == b`|a と b の値が同じかを判定|
|`a <= b`|a が b 以下かを判定|
|`a and b`|論理AND|

:::note info
特に、論理ANDはC言語では `&&` だが、Zig では `and` である。
:::

## 入出力

[Hello World](https://ziglang.org/documentation/0.13.0/#Hello-World)
[String Literals and Unicode Code Point Literals](https://ziglang.org/documentation/0.13.0/#String-Literals-and-Unicode-Code-Point-Literals)
[Anonymous List Literals](https://ziglang.org/documentation/0.10.1/#Anonymous-List-Literals)
[if](https://ziglang.org/documentation/0.13.0/#if)
[Error Union Type](https://ziglang.org/documentation/0.13.0/#Error-Union-Type)
[@bitCast](https://ziglang.org/documentation/0.10.1/#bitCast)
[@truncate](https://ziglang.org/documentation/0.10.1/#truncate)
[std.io](https://ziglang.org/documentation/master/std/#std.io)
[std.fs.File](https://ziglang.org/documentation/master/std/#std.fs.File)
[std.io.Reader](https://ziglang.org/documentation/master/std/#std.io.Reader)
[std.io.Writer](https://ziglang.org/documentation/master/std/#std.io.Writer)

入出力は、標準ライブラリを用いて行うことができる。
以下のようにして、標準ライブラリを `std` として読み込める。

```zig
const std = @import("std");
```

以下のようにすると、標準出力に出力する用の `Writer` オブジェクト `stdout` を取得できる。

```zig
const stdout = std.io.getStdOut().writer();
```

この `Writer` の `print` メソッドにより、データを出力できる。

```
try stdout.print(書式指定文字列, .{データリスト});
```

書式指定文字列では、`{d}` と書くとデータ(数値)を十進数で出力でき、`{c}` と書くとデータ(8ビットの数値)を文字として出力できる。

C言語の `getchar()` が返すように `i32` (符号付き32ビット整数) に `-1` (EOF) または1バイトを表す 0～255 の整数が格納されているとき、(EOF が入っている可能性は無視して) この1バイトを出力するのは以下のようにできる。

1. `@bitCast` により、`i32` のデータを同じバイト列で表される `u32` (符号なし32ビット整数) に変換する
2. `@truncate` により、整数の上位ビットを切り捨てて `u8` に変換する
3. 変換した値を出力する

```zig
const ch: i32 = 0x30;
try stdout.print("{c}", .{@truncate(u8, @bitCast(u32, ch))});
```

:::note info
`@bitCast` および `@truncate` は、現在最新の 0.13.0 では引数を1個だけとり、変換先の型は変換結果を使う側が受け入れる型によって決まる。
しかし、AtCoder で用いられる 0.10.1 では引数を2個とり、変換先の型を明示する。
:::

以下のようにすることで、標準入力から1バイト読み込み、数値として取得できる。

```zig
fn readChar() i32 {
    const stdin = std.io.getStdIn().reader();
    const c = stdin.readByte();
    if (c) |value| {
      return value;
    } else |_| {
      return -1;
    }
}
```

まず、`std.io.getStdIn().reader()` により読み込み用のオブジェクトを取得する。
次に、`stdin.readByte()` によりこのオブジェクトを用いて1バイト読み込む。
これは `NoEofError!u8` という型のデータを返す。
この型は、エラーのデータと通常のデータが重ね合わせになっているので、`if` 文を用いて分離する。

# 提出コード

```zig
const std = @import("std");

fn readChar() i32 {
    const stdin = std.io.getStdIn().reader();
    const c = stdin.readByte();
    if (c) |value| {
      return value;
    } else |_| {
      return -1;
    }
}

fn readInt() i32 {
    var value: i32 = 0;
    while (true) {
        const c = readChar();
        if ('0' <= c and c <= '9') {
            value = value * 10 + c - '0';
        } else {
            return value;
        }
    }
}

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();
    const a = readInt();
    const b = readInt();
    const c = readInt();
    try stdout.print("{d} ", .{a + b + c});
    while (true) {
        const ch = readChar();
        try stdout.print("{c}", .{@truncate(u8, @bitCast(u32, ch))});
        if (ch == '\n') {
            break;
        }
    }
}
```

[提出 #54529743 - AtCoder Beginners Selection](https://atcoder.jp/contests/abs/submissions/54529743)