Rust-scriptで始めるシンプルなRustスクリプティング

## はじめに

Rustは高いパフォーマンスと安全性を兼ね備えた言語として人気ですが、通常はCargoプロジェクトを作成し、コンパイルするという手順が必要です。小さなタスクやスクリプト的な用途ではこの手順が煩わしく感じることもあります。

そこで今回は「rust-script」というツールを紹介します。これを使えば、PythonやBashのようにRustのコードをスクリプトとして直接実行できるようになります。特に、コマンドライン操作の自動化や小さなユーティリティーの作成に最適です。

下記記事でも登場していました

https://qiita.com/yonaka15/items/2bca8d43267d84c3ec32


## 環境準備

rust-scriptはCargoを使って簡単にインストールできます。

```bash
cargo install rust-script
```

Rust 1.74以降が必要です。

## 基本的な使い方

### シンプルなスクリプト

まずは簡単な例から見てみましょう。以下の内容を`hello.rs`というファイル名で保存します。

```rust
println!("Hello, World!");
```

そして、ターミナルで次のコマンドを実行します。

```bash
rust-script hello.rs
```

出力:
```
Hello, World!
```

通常のRustプログラムでは`fn main() { ... }`が必要ですが、rust-scriptでは自動的に追加されるので省略できます。もちろん、明示的に書くこともできます。

### 依存パッケージを使ったスクリプト

外部のクレートを使用したいときは、ファイルの先頭にカーゴの依存関係を記述します。以下の二つの方法があります。

**方法1: コメントで依存関係を指定**

```rust
// cargo-deps: time="0.1.25"
fn main() {
    println!("{}", time::now().rfc822z());
}
```

**方法2: ドキュメントコメントでCargo.tomlの一部を記述**

```rust
#!/usr/bin/env rust-script
//! This is a regular crate doc comment, but it also contains a partial
//! Cargo manifest.
//!
//! ```cargo
//! [dependencies]
//! time = "0.1.25"
//! ```
fn main() {
    println!("{}", time::now().rfc822z());
}
```

この例では`time`クレートを使って現在時刻をRFC822形式で出力しています。複数の依存関係がある場合は、カンマで区切って指定します。

```rust
// cargo-deps: time="0.1.25", serde="1.0", tokio="1.0"
```

## 実行可能スクリプトとして設定する

### Unixシステムの場合

Unixシステム（LinuxやmacOS）では、シバン行を使って直接実行可能なスクリプトを作成できます。ファイルの先頭に以下を追加し、実行権限を付与します。

```rust
#!/usr/bin/env rust-script
println!("This is an executable Rust script!");
```

```bash
chmod +x script_name
./script_name
```

### Windowsの場合

Windowsでは、`.ers`拡張子（executable Rust）をrust-scriptと関連付けることができます。

```bash
rust-script --install-file-association
```

これで`.ers`拡張子のファイルを直接実行できるようになります。関連付けを解除するには：

```bash
rust-script --uninstall-file-association
```

クロスプラットフォーム対応の場合は、シバン行を追加して`.ers`拡張子を使用するとよさｓ。

## 応用例：コマンドラインパイプラインの作成

rust-scriptは単独で使えるだけでなく、他のコマンドラインツールと組み合わせることで真価を発揮します。特に「duct」クレートと組み合わせると、複雑なコマンドライン処理を簡潔に記述できます。

以下は、ductを使ってファイル一覧を取得し、特定の条件でフィルタリングする例です：

```rust
#!/usr/bin/env rust-script
//! ```cargo
//! [dependencies]
//! duct = "0.13"
//! ```

use duct::cmd;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    // lsコマンドを実行して結果を取得
    let files = cmd!("ls", "-la").read()?;
    
    // 結果を行ごとに処理
    for line in files.lines() {
        // 「.rs」を含む行だけを表示
        if line.contains(".rs") {
            println!("Rust file found: {}", line);
        }
    }
    
    Ok(())
}
```

ductの特長は、シェルのようにコマンドをパイプで接続できることです：

```rust
#!/usr/bin/env rust-script
//! ```cargo
//! [dependencies]
//! duct = "0.13"
//! ```

use duct::cmd;

fn main() {
    // findコマンドでRustファイルを検索し、wcでカウント
    let count = cmd!("find", ".", "-name", "*.rs")
        .pipe(cmd!("wc", "-l"))
        .read()
        .unwrap();
    
    println!("Rustファイルの数: {}", count.trim());
}
```

## 便利な使い方

### ワンライナー式の評価

引数として与えた式を直接評価することも可能です：

```bash
rust-script -e '1+2'
```

出力:
```
3
```

依存クレートも指定できます：

```bash
rust-script --dep chrono -e "chrono::Utc::now().to_string()"
```

### 標準入力のフィルタリング

`--loop`オプションを使うと、標準入力の各行に対して処理を適用できます：

```bash
cat some_file.txt | rust-script --loop "move |line| println!(\"{}\", line.to_uppercase())"
```

この例では、入力されたテキストをすべて大文字に変換しています。

## 実行速度について

1回目の実行には少し時間がかかりますが、コンパイル済みのキャッシュを活用するため、2回目以降の実行は非常に高速です。

## まとめ

rust-scriptを使うと、Rustの高度な機能を保ちながらもスクリプト言語のような手軽さでコードを実行できます。

- 小さなツールやユーティリティの素早い開発
- シェルスクリプトやPythonスクリプトの代替として
- Rustの強力な型システムと安全性を活かしつつ、簡潔に書ける
- コマンドラインツールとの親和性が高い

特に「duct」クレートと組み合わせると、コマンドラインの操作を安全かつ簡潔に書けるのがメリットです。

Rustをもっと日常的に使いたい方や、小さなスクリプトを書く場合は、ぜひrust-scriptを試してみてください。

## 参考リンク

- [rust-script公式サイト](https://rust-script.org/)
- [ductクレートのドキュメント](https://docs.rs/duct/latest/duct/)
- [rust-scriptのGitHubリポジトリ](https://github.com/fornwall/rust-script)