2022.03.03
STAFF BLOG
スタッフブログ
TECHNICAL
テクログ
こんにちは。たなしょです。
趣味でLinuxを触ったり、TUIツールを作成をしています。
最近Rustを触り始めて一通りの文法事項がわかってきたので、こちらの本に記載のあるCで書かれているプログラムをRustに書き換える遊びをしています。
今回はcatコマンドを題材にCからRustに書き換える際に詰りやすいポイントやRustならではの書き方について触れていければなと思います。
今回作るcatコマンドについて
今回作るcatコマンドはLinuxに標準についているcatの一部を抜粋したものを作成します。
LinuxのMan pageに記載のあるcatコマンドはオプションを付与することでいろいろできますが、
全てを実装するのは大変なので今回は基本性能である、
・指定したファイルそれぞれの内容を標準出力へ書き出す。
・FILE が一つも与えられないと標準入力から読み込む。
上記二つの機能を実装した「なんちゃってcatコマンド」を作成していきます。
実際Rustに書き換えてみた
Rustで「なんちゃってcatコマンド」を実装してみました。
以下がソースコードになります。
https://github.com/jacoloves/lab/blob/master/rust_project/hutulina_rust/cat_rust/src/main.rs
各関数を軽く説明していくと、
main()はコマンドライン引数を読み込んで、引数が2つ未満ならdo_stdout()(標準入力を読み込み、標準出力に返す関数)を実行します。引数が2つ以上の場合はdo_cat()(ファイルを読み込んで、 標準出力に書き込む関数)を実行します。
do_cat()はシステムコールのopen、read、write、closeを使用します。
手順を簡単に書くと下記のような順番で処理をしています。
- openで読み取り専用でファイルを開きます。
- readでファイルの中身を1行ずつ読み取ります。
- 読み取ったデータを標準出力に書き込んでいます。
- ファイルが全て読み取られるとcloseでファイルを閉じます。
do_stdout()はreadで標準入力を受け取り、writeで標準出力に書き込んでいます。
die()はエラーが発生した際のエラー処理を出力します。
苦労した、大変だった点
- クレートの選別
Cの場合システムコールのMan pageに必要なヘッダーファイルが書いてあるのでそれをそのまま書けばいいのですが、Rustにはヘッダーファイルが存在しないのでヘッダーファイルに記載のある処理が呼び出せるクレートを調べました。
色々調べたところlibcというクレートには色々なシステムコールが入っていそうだったのでlibcを使用しました。 - unsafeスコープ
システムコールを使用した処理を書いていると高い確率でunsafeをつけるようにコンパイラから警告がでます。unsafeを言われたとおりにつければ回避できるのですが、闇雲につけるとソースコードが見づらくなりエラーになってしまいます。
例えば、libcにある変数名で宣言した変数をunsafeスコープ外でも使用してしまいスコープ範囲外エラーになることがしばしばありました。
上記のことがあったのでlibcを多用する関数では初めからunsafeスコープで囲うことで、関数内で自由に変数を使えるように対処しました。
公式でも記載のあるとおり、低レベルなシステムプログラミングを取り組むにはunsafeと長く付き合っていくことになりそうです。 - CとRustで使われている型が違う
CとRustではわれている型が違うためそこの穴を埋めるのに苦労しました。
型の多くはlibcに定義されている型をキャストすることで対応しましたが、
unsined char buf[BUFFER_SIZE]のようなchar配列はRustには存在しないため、String型で代用しようとしたところ読み込み処理終了間際に不正メモリアクセスでセグフォになってしまいました。
最終的にlet buf: *mut libc::c_void = libc::malloc(BUFFER_SIZE)のようにc_voidEnumにmallocでBUFFER_SIZEバイト分メモリを確保して、処理の最後にメモリをfreeする方法で処理を実装しました。
最後に
CからRustへの書き換えはいかがだったでしょうか?
始めはとっつきにくいCからRustへの変換ですが、慣れればRustのソースコードの良さに気づけるかと思います。
これを機会にRustに触れてみてはいかがでしょうか。
最後までお読みいただきありがとうございました。