Code Reading:Redox(Rust)版coreutilsのcatコマンド その1(全2回)

前書き

Rustを学習するための一環として、Redox(OS)版coreutilsのcatコマンドをCode Readingします。Redoxプロジェクトや環境構築方法に関しては、以下の記事にまとめてあります。

環境構築:Redox向けcoreutils(Rust)のCode Reading準備およびReading対象コマンド一覧

catコマンドは、記事を2つに分けて説明します。今回(その1)は、catコマンドのオプションパース処理までを説明し、その2でcatコマンドの主要な処理(ファイル内容の表示)を説明します。

                             

catコマンド 挙動のおさらい

ソースコードの確認に入る前に、catコマンドの挙動を確認します。catコマンドは、由来である”concatenate(〜を連結する)”の通り、ファイルの中身を連結します。連結操作よりも、ファイルの中身を表示するために用いる事が多いと思います。

以下に、実行例を示します。Redox版coreutils(Rust版coreutils)を用いていますが、GNU版/BSD版と挙動は同じです。

                                  

catコマンドのコード全文(Reading対象)

catコマンドは約270Stepと短いですが、本記事では全文を示しません。全文を確認する場合、Redox版coreutilsの公式リポジトリを参照して下さい。ただし、Reading結果を示す際は、部分的なコードを示します。

                                

一行目:#![deny(warnings)]について

以下に示すcat.rsを示します。最初の部分は、”#![deny(warnings)]”、crateの読込(extern部分)、モジュールの読込(use部分)で開始しています。crate・モジュールの読み込みは一般的な文法のため、一行目のみ説明します。

“#![deny(warnings)]”によって、コンパイラがWarningを出した場合、強制的にエラー(ビルド失敗)扱いにします。安定したバイナリを提供するために有効な設定に思えますが、現段階では推奨されていません。その理由は、2019年現在のRustが安定した仕様ではなく(仕様変更が多く)、下方互換性に関するWarningが出やすいためです(参考情報:その1その2)。

2019年現在のRustは、構文と標準ライブラリAPIのみが安定しており、バージョンが上がると非推奨API(obsolete API)が増えます。非推奨APIは、ビルド時にWarningとして指摘されます。つまり、#![deny(warnings)]を記述している場合、バージョンアップ後にビルドが壊れる可能性があります。

                        

なお、#![deny(warnings)]を使用せずに、Warning発生時にビルドエラーを発生させる方法は、

  1. CI時(正確にはテスト時のみ)に有効化する方法
  2. ビルド時オプションによる方法

の2通りがあります(正確には、他の方法もあります)。順に、方法を説明します。

1. CI時(正確にはテスト時のみ)に有効化する方法

Cargo.tomlに[features]以下の内容を記載し、crate(lib.rsやmain.rs)のトップにcfg_attr(条件付き属性)を記載します。

cfg_attr(条件付き属性)は、第一引数(今回はfeature=”strict”)が有効になっている場合、”#[deny(warnings)]”と同じ扱いになります。第一引数が無効の場合、この処理は何もしません。

第一引数を有効化するために、テスト時のコンパイルは、以下のようにcargoを実行します。この記載は、strict機能を有効化しています。

                                      

2.ビルド時オプションによる方法

変数RUSTFLAGS経由で、cargoが呼び出すコンパイラに-D(deny)オプションを付与する事によって、Warning時にビルドエラーを発生させます。具体的には、以下の形式でビルドします。

              

main関数における標準出力・標準エラーの設定

以下に示すmain関数内で、標準出力へのハンドラ(変数stdout)、標準エラーのハンドラ(変数stderr)を取得します。それぞれ、バッファに関する仕様が異なります。

  • 標準出力ハンドラは、mutex(排他制御)によってアクセス同期される共有グローバルバッファへの参照
  • 標準エラーハンドラは、バッファリングされない

stdout.lock()“は、明示的にアクセスを同期させます。ハンドラを標準ストリームにmutexでロックし、書き込み可能な状態にします。一度だけロックを取得した方が、複数回ロックを取得するよりも高速で動作します。このロックは、ハンドラがスコープ範囲外となった場合(今回であればmain関数を抜けた場合)、解除されます。

同様に、”stderr.lock()”も存在しますが、使われていません。エラーメッセージは量が少ないため、明示的な同期が不要という判断でしょうか?

                                       

                 

main関数における構造体Programの初期化

main関数の残りの部分は、構造体Programの初期化(initialize())を実行後、メインとなる処理(and_execute())を実行するだけです。and_execute()の実行結果(終了ステータス)を引数として、現在のプロセスをexit()で終了させます。

構造体Programが持つメンバの意味合い(用途)は、下表の通りです。

メンバ名 用途
exit_status catコマンドの終了ステータス
number 表示対象ファイルの行番号を表示するためのフラグ
number_nonblank 表示対象ファイルの行番号(空白行を除く)を表示するためのフラグ
show_ends 行末を表示するためのフラグ
show_tabs Tabを表示するためのフラグ
show_nonprinting 非表示文字を表示するためのフラグ
squeeze_blank 連続した空行を一行に変更するためのフラグ
paths 表示もしくは連結対象のファイルPATH

初期化処理(initialize())の実装は、以下の通りです。initialize()は、実装の上に書かれているコメント通り、引数・フラグの初期化をしています。上から順番に説明します。

                        

まず、引数の初期化部分(以下に示したコード)です。引数をパースするcrateは、Redoxプロジェクトが独自に作成したものです。この処理では、以下の内容を順番に実行しています。

  1. オプションが10個として、ArgParserのコンストラクタ(new())を実行
  2. add_flag()でオプションを追加(第一引数=shotオプション、第二引数=longオプション)
  3. 標準API経由(env::args())で、catコマンドの実行時オプションをパース
Shortオプション Longオプション 機能
A show-all 全ての非表示文字(行末、Tab、CR)を表示
b number-nonblank 空白行を除いて、行番号を付与
e なし Tabを除いて、全ての非表示文字(行末、CR)を表示
E show-ends 行の最後に”$”を表示
n number 行番号を付与
s squeeze-blank 連続した空行を一行に変更
t なし TABを”^I”で表示
T show-tabs TABを”^I”で表示
v show-nonprinting 非表示文字をキャレット記法などで表示
h help ヘルプを表示

初期化処理の残り部分は、catコマンドのオプションパース結果をもとに、各フラグを有効化し、表示・連結対象のファイルパスを保持するだけです。helpの表示のみ(以下の実装のみ)、説明します。

オプションパーサで”help”(もしくは”h”)を見つけた場合、helpの表示処理に移ります。正常系の処理は、以下のように、コードを読んだ通りです。

  • 標準出力に変数MAN_PAGE(後述)をバイト列として出力
  • ストリームバッファのフラッシュ(標準出力への書き出し)
  • 正常終了

異常系の処理では、try()によるエラーハンドリングをしています。このtry()は、昔のバージョンで?演算子を用いてResult型(エラーかどうかを示す列挙型)を返していた処理と同等です。エラーハンドリングに用いる型を柔軟にするための処理のようですが、公式ドキュメントに詳細な説明がなかったため、割愛します。

変数MAN_PAGEは、以下のように、help文をそのままハードコーディングされています。

変数MAN_PAGEは、&’static str型なので、文字リテラル(バイト列)としてバイナリ中に埋め込まれます。また、文字リテラルは、r#”文字列”#という形式で表記されています。この形式は、Raw(生)の文字リテラルを定義するために使います。この形式を用いる事によって、エスケープ文字(“\”)なしで、文字リテラル内の特殊文字(エスケープシーケンス)を通常の文字として扱えます(参考)。

                                  

Reading結果 その2へのリンク

Code Reading:Redox(Rust)版coreutilsのcatコマンド その2(全2回)

                          

おすすめ