🧰

開発用適当ツールは Rust で作るのもオススメ

2024/10/16に公開
10

開発用適当ツールは Go で作るのがオススメ!?

先日、開発用適当ツールはGoで作るのがオススメ という記事を拝見しました。

まだ読んでないよという方はぜひ読んでみてください!

https://qiita.com/ssc-ksaitou/items/6c66669f1672806ac9bb

とても良い記事でした😌✨

Go 言語も CLI ツールの実装に向いているということも分かりました。

そして、Go 言語の魅力も伝わってきました...!!

まとめると以下のような点がメリットとして挙げられていると思います。

  1. go run で簡単に実行できる
  2. シングルバイナリにクロスコンパイルできる
  3. go.mod / go.sum が依存性管理を楽にしてくれる
  4. 動作速度も申し分なし

たしかに開発用適当ツールの作成というユースケースは Go は魅力的な選択肢だと思います!

開発用適当ツールは Rust で作るのもオススメ

前置き

最初に大事なことを言っておきます。

タイトルにもあるように、Rust も であって GO よりも ではありません!

開発用適当ツールは 好きな言語で、開発環境に合った言語で 実装するのがベストだと思います。

Rust で作るのもオススメ

上述した Go で作るメリットですが、Rust にも当てはまっている部分が多いと思います。

  1. cargo run で簡単に実行できる
  2. シングルバイナリにクロスコンパイルできる
  3. Cargo が依存性管理を楽にしてくれる -- Cargo.toml / Cargo.lock
  4. 爆速!!!! -- 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.tomldefault-run を追加しましょう。

Cargo.toml
[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
rusty-dev-utility/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.tomldefault-members を追加して、デフォルトで実行されるターゲットバイナリを指定することも可能です。

参考: Workspaces - The Cargo Book

utils に実装した共通処理を使いたい場合は、依存関係を明示する必要があります。

hello/Cargo.toml
[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 installnpm install のようなコマンドを実行する必要はありません。

また、Rust や公式のツールは後方互換性を非常に大事にしているので、ツールそのものが壊れて使えなくなる自体を避けやすい傾向にはあると思います。

仮に壊れたとしても、コンパイル時にエラーで教えてくれたり、Clippy が教えてくれたりすると思うので、修正も比較的容易なのではないかと...🤔🤔

(これは希望的予想です笑)

シングルバイナリにクロスコンパイルできる

社内で配布したい場合など、シングルバイナリにコンパイルできる点は大きなメリットです。

単一のファイルだけを配布すればよいため、配布プロセスが非常に簡単です。

また、使うユーザーも単にバイナリファイルをダウンロードして実行するだけで済みます。

また、Rust ではクロスコンパイルも可能です。

  • Docker コンテナ内でビルドする
    • Linux なら Linux のイメージを使って、コンテナ内でビルドする
  • ツールチェインをインストールしてビルドする
  • クロスコンパイルツール cross を使ってビルドする

Windows 向けにツールをビルドする

Windows 向けにツールをビルドするにあたって、Docker コンテナ内でツールチェインをインストールしてビルドしてみました。

Dockerfile
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 まわりのツールでいえば、BiomeRspack など該当するかと思います。

他の CLI ツールに関しても、多くの Rust で実装されたものが存在します。

https://dottrail.codemountains.org/cli-tools-written-in-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

hellogood_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 ツールを公開しています😌

https://zenn.dev/collabostyle/articles/c59ce7e2fad2a3

cargo binstall について

cargo binstallCargo B(inary)Install のことで、GitHub に添付されたビルド済みのバイナリをダウンロードしてくれます。

ビルド時間が不要です!素晴らしい!

https://github.com/cargo-bins/cargo-binstall

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

sontixyousontixyou

`` に実装した共通処理を使いたい場合は、依存関係を明示する必要があります。

`` のなかにutilsが抜けてそうです

eleven-junichi2eleven-junichi2

Pythonなら、開発用ツールをパッケージにしてローカルに置いたのをpipx(Pythonパッケージをツールとして導入する想定のpip、既存環境と隔離した環境でパスも通してくれる)経由でインストールするのが良いと思います

山とコード山とコード

ありがとうございます!!!!
とても便利そうなツールですね...😌
試してみます!

また、記事本文にも追記しました!

Public ThetaPublic Theta

Rustでcargo installするときのおそらく一番のネックはビルドする時間がかかることですが、最近はGitHubのリリースなどにあるバイナリを自動で引っ張ってきてくれるcargo binstallがサードパーティとはいえ普及しつつあって、ますます便利になっているのを感じます!

https://github.com/cargo-bins/cargo-binstall

プライベートリポジトリからのインストールについては、私もこの記事を見て同じエラーに遭遇したのですが、例えば次の状態になるように設定すれば、無事インストールできたので、もうご解決済みかもしれませんが、ご報告しておきます。

~/.cargo/config.toml

[net]
git-fetch-with-cli = true

~/.gitconfig

[credential]
    helper = store --file ~/.git-credentials
    # デフォルトなので --file からは無くてもいい

~/.git-credentials

https://GITHUB_USER:GITHUB_TOKEN@github.com

net.git-fetch-with-cliがデフォルトではfalseになっているのと、Gitの認証の設定がされていなかったのが原因だったのですが、確かに少し手間取りました……。

https://doc.rust-lang.org/cargo/appendix/git-authentication.html

https://git-scm.com/book/ja/v2/Git-のさまざまなツール-認証情報の保存

ちなみに、cargo binstallの方もバイナリのリリースを用意して、Cargo.tomlrepositoryが設定された状態であれば、プライベートリポジトリからのインストールにも成功したのでよろしければお試しください!

[package]
repository = "https://github.com/..."
山とコード山とコード

詳細なコメントありがとうございます🙌✨
cargo binstall は、最近たしかに目にするようになってきた気がします!
便利な良いツールだと思いますので、追記しました!

また、プライベートリポジトリからのインストール方法については、
このコメントを参照してくださいと追記しました!
私も試してみます。

ありがとうございます!!!!

5t1111115t111111

個人的な感想ですが、Rust はビルド成果物のサイズがかなり大きいので手元で使う適当ツールを量産するのには向いてないな~と感じてしまっています。

一回バイナリにしたらもうビルドし直さないようなものばかりならいいのですが、そうでなければこまめに cargo clean して回らないとディスクを食い尽くしてしまう…

何か工夫されていることはありますか?

山とコード山とコード

コメントありがとうございます!

たしかにビルド後のサイズが大きいと言われることはありますね...。
大量生産する場合は不向きになってくるかもしれません...😭

何か工夫されていることはありますか?

すでに試行錯誤されているかとは思いますが、できることがあるとすればプロファイルでビルドをカスタマイズすることぐらいでしょうか。

自分の場合は適当ツールは使いきりなら捨ててしまうことも多く、
この処理書いてて勉強になったなという時は自分のメモとしてZennで記事にしてから削除しています。
あまり参考にならず、申し訳ないです🙇‍♂️

5t1111115t111111

ありがとうございます。

拝見しました。
それを見て、最終成果物のバイナリサイズを小さくして、それだけを持ちまわって、いっそ開発環境ごと捨てるというのも1つの方法だなと思いました (例えば、Dev Container や GitHub Codespaces とかを使って作って環境ごとそのまま捨てる、みたいな) 。

正攻法で考えると、一応こういうツールはあるのですが、ざっくりと消しちゃうので使いやすくはないのですよね。
cargo-clean-all

と思ったら、こんなのを作っている方がいました。自分の要件にはかなりちょうど良さそうなので試しに使わせてもらおうと思います。
https://zenn.dev/higumachan/articles/d17a8c8693d536

山とコード山とコード

ありがとうございます!

便利なツールですね✨
もっと広まっても良さそう...!
私も使ってみたいと思います。