この記事はEPOCH-NET Advent Calendarの12日目です。
自己紹介
初めまして。11月から株式会社EPOCH-NETでアルバイトをしているEsurioと申します。
今回のアドカレの主催です。
趣味でSNSのサーバー管理などをしたりSNSを改造して遊んだりRustでCLIツールを作ったりしています。
Rustとは
「効率的で信頼できるソフトウェアを誰もがつくれる言語」です。
関数型、手続型、オブジェクト指向などの実装をサポートしているマルチパラダイムな言語であり、CやC++に代わるシステムプログラミング言語を目指しています。
メモリ管理を所有権という機能で管理しており、これによってメモリ安全性を担保しています。所有権を利用するメリットとして、CやC++のように明示的にメモリを確保したり、JavaのようにGCによるメモリ管理をしなくて済む、というものがあります。この機能について詳しく書くと記事1本分になるので、詳しくはこちらをご覧ください。
身の回りのRust
Rustで書かれたものは身近に存在します。
例えば、Firefoxのレンダリングエンジン、一部のWindowsのコンポーネント、Androidのカーネル、Linux 6.1以降のNVMeドライバなどです。
DiscordやDropbox、CloudflareもRustを採用しています。
TypeScriptを使う人にとっても縁があります。npmjs.comもpublish
時のボトルネックを改善するためにRustを使用しましたし、ランタイムの一つであるDenoはRustによって書かれています。
なぜRustか
私がRustを好んで書く理由としては、パフォーマンスと信頼性の高さ、優秀なエコシステムによる生産性の高さ、できることの多さがあります。
パフォーマンス
Rustは実行パフォーマンスが非常に高いです。C++と同程度かそれ以上のパフォーマンスを誇ります。
Rustは直接機械語にコンパイルされるため、CやC++と同程度の実行速度を誇ります。
信頼性
先ほども述べた通り、所有権という仕組みによってメモリ安全性が担保されています。また、型エラーなどが存在する場合コンパイラがエラーを出力するため、バグが比較的少なく済みます。
静的型付けであることも大事です。Rustの強力な型システムによって、Javascriptのような1 + "1" = 11
という不具合が防げます。
優秀なエコシステム
Rustのエコシステムは優秀です。Node.jsにnpmがあるように、RustにはCargoというツールがあります。Node.jsはESLintやPrettierを自分でセットアップする必要がありますが、CargoではデフォルトでLinterとFormatterが同梱されています。さらに、Cargoはサブコマンドを自分自身で実装することで、簡単に拡張可能です。
rust-analyzerという拡張機能も優秀です。rust-analyzerはRustの入力補完や型チェック、文法ミスのチェックなどを行うための言語サーバープロトコルです。この拡張機能のおかげで毎度ビルドせずにコンパイルエラーを確認することができます。Vim/NeovimでもEmacsでも使えます。
できることの多さ
Rustはとにかくできることが多いです。
- CLIツール
- WebAssemblyを利用したフロントエンド開発
- Webサーバーのバックエンド開発
- 組み込み
- OSカーネル開発
- ゲーム開発
- ネイティブアプリケーション開発
ここにあげたこと以外にも様々なことができます。
Rustで遊んでみよう
Rustで実際に遊んでみましょう。
Unix系OSであれば次のコマンドを実行してください。
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Windowsの場合、この手順に従ってください。
これでRustの環境構築は終了です。
Hello worldをやってみましょう。
拡張子を*.rsで作成します。
fn main() {
println!("Hello, world!");
}
コンパイルしてみましょう。gccとrustcが入っている状態で次を実行します。
rustc main.rs
./main
ちゃんと実行できていればコンソール上にHello world!が出力されているはずです。
簡単なCLIツールも作ってみましょう。任意の画像をPNGにするツールです。
完成品はこちら。
ターミナルを開いて次のコマンドを実行してください。
$ cargo new example-cli
$ cd example-cli
$ cargo run
Hello, world!が出力されるはずです。
次に、clapというクレート(=ライブラリ)を追加しましょう。
ターミナルを開いて次を実行します。
$ cargo add clap --features derive
次に、コマンドラインの引数を解析するための構造体を定義します。
use clap::Parser;
#[derive(Parser)]
struct Cli {
// 入力画像のパス
input: String,
// 出力画像のパス
output: String,
}
これで下準備は完了です。
main関数をいじっていきましょう。
中身のprintln!()は削除し、色々書き換えていきます。
画像処理には、今回はimage-rs/imageを使います。
use image::ImageFormat;
use std::path::Path;
use std::process;
fn main() {
// CLIの引数を解析
let args = Cli::parse();
// 画像を読み込む
let img = match image::open(&args.input) {
Ok(img) => img,
Err(e) => {
eprintln!("Error: {}", e);
process::exit(1);
}
};
// 出力先のパス
let output_path = Path::new(&args.output);
// 拡張子をpngに固定する
let output = output_path.with_extension("png");
// 画像を保存
if let Err(e) = img.save_with_format(&output, ImageFormat::Png) {
eprintln!("Error: {}", e);
process::exit(1);
}
println!("Image saved to {}", output.display());
}
これで関数の部分は終了です。現在の状態は次のようになるかと思います。
use clap::Parser;
use image::ImageFormat;
use std::path::Path;
use std::process;
#[derive(Parser)]
struct Cli {
// 入力画像のパス
input: String,
// 出力画像のパス
output: String,
}
fn main() {
// CLIの引数を解析
let args = Cli::parse();
// 画像を読み込む
let img = match image::open(&args.input) {
Ok(img) => img,
Err(e) => {
eprintln!("Error: {}", e);
process::exit(1);
}
};
// 出力先のパス
let output_path = Path::new(&args.output);
// 拡張子をpngに固定する
let output = output_path.with_extension("png");
// 画像を保存
if let Err(e) = img.save_with_format(&output, ImageFormat::Png) {
eprintln!("Error: {}", e);
process::exit(1);
}
println!("Image saved to {}.png", output.display());
}
ビルドして実際に試していきましょう。
cargo run -- input.jpg output
正常に終了すれば、Runningの後に次のように表示されるはずです。
Image saved to output.png
これで一旦完成です。
まとめ
今回は極めてシンプルなCLIツールを作成しました。image-rsは他にも様々な拡張子に対応していますし、clapはより複雑なCLIもシンプルに作成できるため、色々いじって遊んでみてください。
コメントを残す