課題 i260913-01 の計画。Mac ・ Linux へ展開すると決めたため C# 版が土俵を降り、残る選択肢が TypeScript と Rust の 2 系統になった。Rust 版を製品として立てる。
範囲は全部である。14 コマンドと 22 オプション、検証も含める。「待受けだけ」ではない。
| 項目 | 中身 |
|---|---|
| 置き場 | src/cli-rs/(Cargo プロジェクト) |
| 出力 | aichat-rs.exe(root に置く。.gitignore の *.exe で追跡外) |
| コマンド | join wait say recent who waiters dump leave archive archives restore rename + 管理 2 つ (restart stop) |
| 外部クレート | 使わない |
| 定義の出どころ | src/client/options.mjs。export-options.mjs の JSON を埋め込む |
C# 版が Json.cs を 256 行で自前に持っている。同じ判断をここでも採る。
| 得るもの | 引き換えるもの |
|---|---|
| ビルドが速い (実測 457 ms) | JSON パーサと HTTP を自前で書く。200〜300 行ほど増える |
| 実行ファイルが小さい (4.63 MB) | |
| ネットワークなしでビルドできる | |
| 依存の版が動いて壊れることがない |
ローカルルール「CLI を直したら 2 本とも直す」に従う。オプションとコマンドの表を Rust 側に書き写さない。export-options.mjs が出す JSON を include_str! で埋め込み、実行時に読む。
これで CLI が 3 本になる。node ・ C# ・ Rust。食い違いは必ず出るので、突き合わせのテストを先に用意する。目で読んで気づける差ではない (実際、同じ秒に立った待受けの並び順が node と C# で食い違っていた)。
| ファイル | 役目 | 元にするもの |
|---|---|---|
main.rs | 入口。コマンドの振り分けと使い方の表示 | Program.cs |
json.rs | JSON の読み書き | Json.cs |
args.rs | 引数の解析。旗・値・位置引数・廃止したもの | Args.cs |
definition.rs | 埋め込んだ定義の読み取り | Definition.cs |
jst.rs | JST の時刻 | JstTime.cs |
since.rs | --since ・ --before の解析 | SinceParse.cs |
waiters.rs | 待受けを数える・選ぶ | Waiters.cs |
client.rs | HTTP。繋ぎ直しとメンテナンス中の扱い | Client.cs |
commands.rs | 各コマンドの中身 | Commands.cs |
wait_log.rs | 待受けの記録 | WaitLog.cs |
ここだけ既存の 2 本に答えが無い。node 版は PowerShell を起こして一覧を取っているが、Mac ・ Linux に PowerShell は無い。
| OS | 使うもの |
|---|---|
| Windows | tasklist(/fo csv で読む) |
| Mac ・ Linux | ps(-eo pid,command) |
取り出したあとの選び方 (WAITER_PATTERN での絞り込み・残すものと止めるものの分け方)は OS で変わらない。取り方だけを分け、選び方は 1 つにする。
課題 i260912-02 で、node の fetch が Windows で 1 往復ごとに 15 ms 待たされることが分かった。Rust は標準ライブラリの TcpStream に HTTP/1.1 を直接書くので、この問題は起きない。ベンチマーク版で実証済み。
TDD で進める。落ちるテストを見てから実装する。落ちるのを見ていないテストは、通ったのが実装のおかげか、最初から通る書き方だったのかを区別できない。
| 段 | 作るもの | ここで確かめること |
|---|---|---|
| 1 | 土台 (json ・ jst ・ args ・ definition) | JSON が往復する。引数が読める。定義が埋め込まれている |
| 2 | 日時の解析 (since)と待受けの選別 (waiters) | 存在しない日を断る。二重の待受けを正しく選ぶ |
| 3 | HTTP と読み取り系 (recent who dump archives) | サーバーに繋がる。出力が node 版と同じ |
| 4 | 書き込み系 (say join leave wait) | 投稿が届く。待受けが長く粘る |
| 5 | 管理系 (archive restore rename restart stop waiters) | 確認の入力を求める。件数を先に出す |
| 6 | 突き合わせと測り直し | node 版と 1 文字ずつ同じ。待受け中のメモリ |
段ごとにコミットする。3,000 行を超える見込みなので、動く単位で区切る。
6 段すべて終わった。14 コマンドが動き、Rust 側の単体テスト 182 件と、node 版との突き合わせ (繋がない 14 通り ・ 実サーバーへ繋ぐ 54 通り)が通っている。
| 段 | 結果 | 見込みとの違い |
|---|---|---|
| 1 | 土台 (JSON ・ JST ・ 引数 ・ 定義) | 暦の計算も自前になった。外部クレートを使わないので当然だが、見込みに入れていなかった |
| 2 | 日時の解析と待受けの選別 | 正規表現の代わりを手で書く量が思ったより多かった |
| 3 | HTTP と読み取り系 | — |
| 4 | 書き込み系 | — |
| 5 | 管理系 | — |
| 6 | 突き合わせと測り直し | — |
| 書いたこと | 実際 |
|---|---|
| JSON と HTTP を自前で書くと 200〜300 行ほど増える | 3 本で 968 行(JSON 440 ・ HTTP 307 ・ 暦 221。テストを含む)。見込みの 3 倍以上 |
| 3,000 行を超える見込み | 5,297 行。うちテストが 1,621 行で、実装は 3,676 行 (C# 版は 2,985 行) |
| (課題に)機能を足せば増える | ほとんど増えなかった。待受けだけの 111 行で 4.89 MB、14 コマンドで 6.41 MB |
8 件。どれも目で読んで気づける差ではなかった。
| 食い違い | なぜ起きたか |
|---|---|
| 桁の詰め方 | 揃え方が 2 種類あった。使い方の表示は表示幅、who は文字数 (node の padEnd)。「一時切断」は 4 文字で表示幅 8 なので、6 桁に詰めると空白が 0 個と 2 個に分かれる |
| 終了コード | 知らないコマンドは 2 ではなく 1。書き間違いに気づけるようにするため |
| 末尾の空行 | node は console.log がもう 1 つ改行を足す |
| 案内の順序 | ID の検証が環境の表示より先。逆だと、ID を書き忘れたときに案内の頭に別の行が挟まる |
| エラーの粒度 | node は 3 段の正規表現でどれにも当たらなければ「形が違います」の 1 つだけを返す。桁や区切りの誤りを言い分けると文言が食い違う |
突き合わせの道具でも 2 度外した。どちらも「桁を揃えた出力から機械的に値を取る」形で、waiters の pid と archives の番号を取り損ねた。後者では両方が同じ使い方の誤りで終わったのを「一致」と読んだ(偽の一致)。取り直して確かめ直している。
| 実装 | 待受け中の実メモリ | 確保量 | waiters |
|---|---|---|---|
| Rust 版 | 6.41 MB | 1.00 MB | 25 ms |
| C# 版 | 24.89 MB | 18.53 MB | 229 ms |
| bun 版 | 38.31 MB | 162.45 MB | 64 ms |
| node 版 | 54.20 MB | 60.50 MB | 112 ms |
waiters は Windows の API を直に呼ぶ形にした。CreateToolhelp32Snapshot で一覧を取り、PEB を辿ってコマンドラインを読む。C# 版への依存は切れた。詳しくはベンチマーク。
| どこ | 何を守るか | 走らせ方 |
|---|---|---|
Rust の #[cfg(test)] |
関数の単位。JSON の解析、日時の丸め、待受けの選別 | cargo test |
tests/cli-rs.test.mjs |
node 版との食い違い。出力・終了コード・埋め込んだ定義 | 一括実行に乗る (node --test tests) |
後者は tests/cli-cs.test.mjs と同じ形にする。C# 版で食い違いを見つけられたのは、この突き合わせがあったからである。
| 論点 | 決めたこと | なぜ |
|---|---|---|
| 外部クレート | 使わない | C# 版の前例。ビルドが速く、実行ファイルが小さい |
| プロセス一覧 | Windows は tasklist、他は ps | PowerShell は Mac ・ Linux に無い |
| C# 版 | 据え置く。Rust 版が全テストを通るまで落とさない | 動くものを先に消さない |
ベンチマークの wait-rs | 残す | 「最小実装の下限」を示す値として意味がある。製品版とは別物 |
| 論点 | いつ決めるか |
|---|---|
| C# 版を落とすか | Rust 版が全テストを通り、待受け中のメモリを測ってから |
| 既定の CLI をどれにするか | 同上。aichat ・ aichat.cmd が呼ぶ先を変えるかどうか |
| ローカルルールの書き換え | 同上。「CLI を直したら 2 本とも直す」は 2 本を前提にしている |
3 本になる期間は必ず生じる。その間に node 版か C# 版を直したら、Rust 版にも同じ修正が要る。突き合わせのテストが唯一の防波堤になる。