開発用適当ツールは Rust で作るのもオススメ
開発用適当ツールは Go で作るのがオススメ!?
先日、開発用適当ツールはGoで作るのがオススメ という記事を拝見しました。
まだ読んでないよという方はぜひ読んでみてください!
とても良い記事でした😌✨
Go 言語も CLI ツールの実装に向いているということも分かりました。
そして、Go 言語の魅力も伝わってきました...!!
まとめると以下のような点がメリットとして挙げられていると思います。
-
go run
で簡単に実行できる - シングルバイナリにクロスコンパイルできる
-
go.mod
/go.sum
が依存性管理を楽にしてくれる - 動作速度も申し分なし
たしかに開発用適当ツールの作成というユースケースは Go は魅力的な選択肢だと思います!
開発用適当ツールは Rust で作るのもオススメ
前置き
最初に大事なことを言っておきます。
タイトルにもあるように、Rust も であって GO よりも ではありません!
開発用適当ツールは 好きな言語で、開発環境に合った言語で 実装するのがベストだと思います。
Rust で作るのもオススメ
上述した Go で作るメリットですが、Rust にも当てはまっている部分が多いと思います。
-
cargo run
で簡単に実行できる - シングルバイナリにクロスコンパイルできる
- Cargo が依存性管理を楽にしてくれる --
Cargo.toml
/Cargo.lock
- 爆速!!!! -- Rust の強み
加えて、cargo install
を使えば、ツールをビルドしてインストールすることもできます。
(パブリックなツールとして公開できるという限定的なケースも含みますが...笑)
- ソースコードをクローンして、
cargo install --path
でインストールする - Github に公開して、
cargo install --git
でインストールする -
crates.io に公開して、
cargo install
でインストールする
Rust での開発用適当ツールの実装パターン
開発用適当ツールはGoで作るのがオススメ では、以下のような構成になっていました。
project-root-directory/
├── プロジェクトの既存ファイル
├── go.mod
├── go.sum
└── cmd/
├── ツール1/
│ └── main.go
├── ツール2/
│ └── main.go
└── internal/ (もしくは分かりやすいディレクトリ)
└── (main.go に入らなかった共通処理などはここに)
既存のプロジェクトのルート直下に、開発用適当ツールを格納するパターンを想定しているようです。
Rust の場合、どのような構成になるか考えてみました。
-
src/bin
の活用 -
workspace
の活用
src/bin
の活用
src/bin
の活用するパターンでは、以下のような構成になります。
The Cargo Book の Package Layout を参考にしました。
project-root-directory/ # 既存プロジェクトのルート相当
├── main-project/
└── rusty-dev-utility/ # 開発適当ツール群
├── src/
│ ├── bin/
│ │ ├── goodbye/
│ │ │ └── main.rs
│ │ ├── good_morning.rs
│ │ └── hello.rs
│ ├── lib.rs # utils モジュールをアタッチ
│ └── utils.rs # 共通処理などを実装
├── Cargo.lock
└── Cargo.toml
src/bin
ディレクトリに .rs
ファイルを格納することで、ファイル名(拡張子なし)
がターゲット名となります。
また、src/bin
にサブディレクトリを作成することもできます。
その場合、サブディレクトリ内に main.rs
が必要で、ディレクトリ名がターゲット名として認識されます。
開発適当ツールを実行するは、rusty-dev-utility
に移動して --bin
を指定してコマンドを実行すれば OK です!
% cargo run --bin good_morning
Good morning!
% cargo run --bin hello
Hello!
% cargo run --bin goodbye
Goodbye!
ちなみに --bin
を指定しない場合、バイナリが複数存在するため、以下のエラーが発生します。
error: `cargo run` could not determine which binary to run. Use the `--bin` option to specify a binary, or the `default-run` manifest key.
available binaries: good_morning, goodbye, hello
cargo run
のみでも実行したい場合は、Cargo.toml
に default-run
を追加しましょう。
[package]
default-run = "hello"
参考: The Manifest Format - The Cargo Book
workspace
の活用
workspace
の機能を活用すれば、開発適当ツールのディレクトリをツール毎のディレクトリに分割できるのではないかと思います。
project-root-directory/ # 既存プロジェクトのルート相当
├── main-project/
└── rusty-dev-utility/ # 開発適当ツール群
├── good_morning/
│ ├── src/
│ │ └── main.rs
│ └── Cargo.toml # utils のパス依存を追加
├── hello/
│ ├── src/
│ │ └── main.rs
│ └── Cargo.toml # utils のパス依存を追加
├── utils/ # 共通処理などを実装
│ ├── src/
│ │ └── lib.rs
│ └── Cargo.toml
├── Cargo.lock
└── Cargo.toml
[workspace]
resolver = "2"
members = ["good_morning", "hello"]
# specify a default members
# Learn more at: https://doc.rust-lang.org/cargo/reference/workspaces.html#the-default-members-field
# default-members = ["hello"]
開発適当ツールを実行するは、rusty-dev-utility
に移動して --package
を指定してコマンドを実行すれば OK です!
% cargo run --package good_morning
Good morning!
% cargo run --package hello
Hello!
src/Cargo.toml
に default-members
を追加して、デフォルトで実行されるターゲットバイナリを指定することも可能です。
参考: Workspaces - The Cargo Book
utils
に実装した共通処理を使いたい場合は、依存関係を明示する必要があります。
[dependencies]
utils = { path = "../utils" }
独自プロジェクトに切り出す
既存プロジェクトの肥大化を避けたい場合などは、独自のプロジェクトに切り出しても良いかなと思います!
おすすめはシンプルな workspace
を活用した構成でしょうか。
rusty-dev-utility-workspace/
├── hello/
│ ├── src/
│ │ └── main.rs
│ └── Cargo.toml
├── good_morning/
│ ├── src/
│ │ └── main.rs
│ └── Cargo.toml
├── Cargo.lock
└── Cargo.toml
Rust で作るメリット
実行が容易
cargo run
コマンドですぐに実行できます。
cargo run
でライブラリであるクレートのダウンロード、ビルド、実行までしてくれます!
実行手順は「cargo run
を実行してください」で OK です。
pip install
や npm install
のようなコマンドを実行する必要はありません。
また、Rust や公式のツールは後方互換性を非常に大事にしているので、ツールそのものが壊れて使えなくなる自体を避けやすい傾向にはあると思います。
仮に壊れたとしても、コンパイル時にエラーで教えてくれたり、Clippy が教えてくれたりすると思うので、修正も比較的容易なのではないかと...🤔🤔
(これは希望的予想です笑)
シングルバイナリにクロスコンパイルできる
社内で配布したい場合など、シングルバイナリにコンパイルできる点は大きなメリットです。
単一のファイルだけを配布すればよいため、配布プロセスが非常に簡単です。
また、使うユーザーも単にバイナリファイルをダウンロードして実行するだけで済みます。
また、Rust ではクロスコンパイルも可能です。
- Docker コンテナ内でビルドする
- Linux なら Linux のイメージを使って、コンテナ内でビルドする
- ツールチェインをインストールしてビルドする
- クロスコンパイルツール cross を使ってビルドする
Windows 向けにツールをビルドする
Windows 向けにツールをビルドするにあたって、Docker コンテナ内でツールチェインをインストールしてビルドしてみました。
FROM rust:latest
RUN apt update
RUN apt upgrade -y
RUN apt install -y g++-mingw-w64-x86-64
RUN rustup target add x86_64-pc-windows-gnu
RUN rustup toolchain install stable-x86_64-pc-windows-gnu
WORKDIR /app
CMD ["cargo", "build", "--release", "--target", "x86_64-pc-windows-gnu"]
カレントディレクトリをバインドしてコンテナ内でビルドします。
docker build . -t rust_cross_compile/windows
docker run --rm -v $(pwd):/app rust_cross_compile/windows
成功すると /target/x86_64-pc-windows-gnu/release
に .exe
がビルドされました。
Windows PC に送りつけて実行させてみました!
正常に動作しているようで何より!
Cargo が依存性管理を楽にしてくれる
Rust のパッケージマネージャである Cargo が依存性管理を楽にしてくれます。
Cargo.toml
には、使用しているライブラリが定義されていますし、ビルド時の設定等を追加することもできます。
Cargo.lock
は依存関係の正確なバージョンを記録しており、再現可能なビルドを保証してくれます。
爆速ツールを作れる
Rust で実装されたツールたちは高速に動作します。
Node.js まわりのツールでいえば、Biome や Rspack など該当するかと思います。
他の CLI ツールに関しても、多くの Rust で実装されたものが存在します。
開発適当ツールで爆速に動作することが求められる場合、Rust が良いのではないでしょうか?!笑
cargo install できる
cargo install
を使えば、作ったツールをコンパイルしてインストールでき、どこからでも実行できるようにすることが可能です。
ビルドしてできたバイナリのパスを通すという面倒な作業はありません!!!
また、わざわざソースコードをダウンロードしなくてもツールをインストールすることもできます。
--path でパスを指定
ソースコードをダウンロードして cargo install --path
を実行します。
■ src/bin
構成の場合
インストールすると、good_mornig
, hello
, goodbye
のコマンドが使用可能になります。
cd ./rusty-dev-utility
cargo install --path .
% good_morning
Good morning!
% hello
Hello!
% goodbye
Goodbye!
また、--bin
でバイナリターゲットを指定することも可能です。
cargo install --path . --bin hello
アンインストール
アンインストールは以下のコマンドを実行するだけです。
cargo uninstall rusty-dev-utility
※ hello
や good_mornig
ではないので、注意。
■ workspace
構成の場合
cd ./rusty-dev-utility
cargo install --path hello
% hello
Hello!
アンインストール
アンインストールは以下のコマンドを実行するだけです。
cargo uninstall hello
--git で Git URL を指定
■ 単体のツール
GitHub 等にソースコードをアップしておけば、--git
で Git URL を指定するだけでツールをインストールできます!
試しに Clap を使って、簡単な CLI ツールとして開発用適当ツールを実装してみました。
GitHub: https://github.com/codemountains/rusty-dev-cli-utility
試しにインストールしてみましょう!
cargo install --git https://github.com/codemountains/rusty-dev-cli-utility
あとはツールを実行するだけです。
% rusty-dev-cli-utility -g Hi Alice
Hi, Alice!
Nice to meet you.
How are you doing?
アンインストール
アンインストールは以下のコマンドを実行するだけです。
cargo uninstall rusty-dev-cli-utility
error: failed to fetch into: /Users/Taro/.cargo/git/db/rusty-dev-cli-utility-xxxx
Caused by:
failed to authenticate when downloading repository
* attempted to find username/password via git's `credential.helper` support, but failed
if the git CLI succeeds then `net.git-fetch-with-cli` may help here
https://doc.rust-lang.org/cargo/reference/config.html#netgit-fetch-with-cli
Caused by:
failed to acquire username/password from local configuration
■ workspace
の場合
以下のコマンドのように、バイナリターゲットを指定する場合があります。
cargo install --git https://github.com/codemountains/rusty-dev-utility-workspace hello
アンインストール
アンインストールは以下のコマンドを実行するだけです。
cargo uninstall hello
crates.io からインストールする
crates.io では、ライブラリであるクレートだけでなくバイナリ(アプリケーション)をアップロードして公開することもできます。
Rust 製の CLI ツールで有名なものに ripgrep (rg) という grep ツールがあります。
ripgrep は以下のコマンドでもインストールすることができます。
cargo install ripgrep
crates.io への公開は非常に簡単で、自分も CLI ツールを公開しています😌
cargo binstall について
cargo binstall
は Cargo B(inary)Install のことで、GitHub に添付されたビルド済みのバイナリをダウンロードしてくれます。
ビルド時間が不要です!素晴らしい!
Rust 以外で開発用適当ツールを作る
Python しか勝たん
情報量の多さ、ライブラリの豊富さ、書いてすぐに実行できる点は素晴らしいと思います。
開発用適当ツールの適当を "テキトー" と捉えるなら、Python が最も相性が良いように感じます。
ちょっとこんなことしたいな... で検索すると、なんでもヒットします。
AI に聞いても、良い回答が得られるでしょう。
1人で使う場合やツールの寿命が短い場合など、Python で雑に実装が一番早いなと思います!
ただ、Python 自体のバージョン管理や依存関係の管理が大変と聞きます。
(個人で開発用適当ツールを作っていて、あまり困ったことないですが...)
最近では、uv という Python パッケージ管理ツールが良いなんて話も聞きます。
ちなみに、uv は Rust で実装されており、Python 版 Cargo を目指しているそうです!
Rustのパッケージマネージャである「Cargo」をPythonにもたらすという意味で、「Cargo for Python」になることを追求しており、高速で信頼性が高いパッケージ管理ツールになることを掲げています。
引用: Rust製のPythonパッケージ管理ツール「uv」を使ってみよう | gihyo.jp
TypeScript の資産を活用したい
既存のプロジェクトが TypeScript であることもあるでしょうし、その資産を活用・流用したいと思うこともあります。
自分も TypeScript のコードを流用して、開発用適当ツールを作成することも多いです。
そんなときのために、サクッと TypeScript のコードを実行できるプロジェクトを用意しておくと便利です!
mkdir node-ts-utility
cd node-ts-utility
npm init -y
npm i -D typescript tsx
npx tsc --init
touch index.ts
echo "console.log('Hello, world!')" > index.ts
npx tsx index.ts
まとめ
開発用適当ツールは Rust で作るのもオススメです!!!!
Rust の魅力が少しでも伝われば幸いです。
Discussion
`` のなかにutilsが抜けてそうです
ありがとうございます😭✨
修正しました!!!
Pythonなら、開発用ツールをパッケージにしてローカルに置いたのをpipx(Pythonパッケージをツールとして導入する想定のpip、既存環境と隔離した環境でパスも通してくれる)経由でインストールするのが良いと思います
ありがとうございます!!!!
とても便利そうなツールですね...😌
試してみます!
また、記事本文にも追記しました!
Rustで
cargo install
するときのおそらく一番のネックはビルドする時間がかかることですが、最近はGitHubのリリースなどにあるバイナリを自動で引っ張ってきてくれるcargo binstall
がサードパーティとはいえ普及しつつあって、ますます便利になっているのを感じます!プライベートリポジトリからのインストールについては、私もこの記事を見て同じエラーに遭遇したのですが、例えば次の状態になるように設定すれば、無事インストールできたので、もうご解決済みかもしれませんが、ご報告しておきます。
~/.cargo/config.toml
~/.gitconfig
~/.git-credentials
net.git-fetch-with-cli
がデフォルトではfalse
になっているのと、Gitの認証の設定がされていなかったのが原因だったのですが、確かに少し手間取りました……。ちなみに、
cargo binstall
の方もバイナリのリリースを用意して、Cargo.toml
にrepository
が設定された状態であれば、プライベートリポジトリからのインストールにも成功したのでよろしければお試しください!詳細なコメントありがとうございます🙌✨
cargo binstall
は、最近たしかに目にするようになってきた気がします!便利な良いツールだと思いますので、追記しました!
また、プライベートリポジトリからのインストール方法については、
このコメントを参照してくださいと追記しました!
私も試してみます。
ありがとうございます!!!!
個人的な感想ですが、Rust はビルド成果物のサイズがかなり大きいので手元で使う適当ツールを量産するのには向いてないな~と感じてしまっています。
一回バイナリにしたらもうビルドし直さないようなものばかりならいいのですが、そうでなければこまめに
cargo clean
して回らないとディスクを食い尽くしてしまう…何か工夫されていることはありますか?
コメントありがとうございます!
たしかにビルド後のサイズが大きいと言われることはありますね...。
大量生産する場合は不向きになってくるかもしれません...😭
すでに試行錯誤されているかとは思いますが、できることがあるとすればプロファイルでビルドをカスタマイズすることぐらいでしょうか。
自分の場合は適当ツールは使いきりなら捨ててしまうことも多く、
この処理書いてて勉強になったなという時は自分のメモとしてZennで記事にしてから削除しています。
あまり参考にならず、申し訳ないです🙇♂️
ありがとうございます。
拝見しました。
それを見て、最終成果物のバイナリサイズを小さくして、それだけを持ちまわって、いっそ開発環境ごと捨てるというのも1つの方法だなと思いました (例えば、Dev Container や GitHub Codespaces とかを使って作って環境ごとそのまま捨てる、みたいな) 。
正攻法で考えると、一応こういうツールはあるのですが、ざっくりと消しちゃうので使いやすくはないのですよね。
cargo-clean-all
と思ったら、こんなのを作っている方がいました。自分の要件にはかなりちょうど良さそうなので試しに使わせてもらおうと思います。
ありがとうございます!
便利なツールですね✨
もっと広まっても良さそう...!
私も使ってみたいと思います。