💨

rust-scriptは今更ながら便利

2023/12/05に公開

概要

Rustを手軽に実行するツール、rust-scriptを紹介します。

  • 1ファイルで済む
  • Cargoプロジェクトを作らずとも、外部ライブラリを簡単に利用できる

のが利点です。

Rustのライブラリ試したい時とかにいいかもね。

rust-script

rust-scriptというツールがあります。
プログラミング言語Rustをスクリプト言語風に実行できます。
https://rust-script.org/
ちょろっとRust書きたいな、という場面で便利に使えたので紹介します。

この記事は TDU CPSLab AdventCalender 2023、5日目の記事です。

インストール

rust-scriptはcargo installでインストールできます。執筆時点でRust 1.64以上が必要です。

$ cargo install rust-script

もしRustを新規にインストールするなら、パッケージマネージャ経由ではなくrustupでのインストールを強く推奨します。cargoも一緒についてきます📦

使い方

Rustのファイルを作成し、ファイルのはじめにshebangをつけるだけ。

hello.rs
#!/usr/bin/env rust-script
fn main() {
  println!("hello");
}

実行方法は、実行権限を付与して実行するか、rust-scriptコマンドに渡します。

$ chmod +x hello.rs
$ ./hello.rs

$ rust-script hello.rs
hello

おわり。(簡単!)

外部クレートの指定方法

補足: Rustについて

Rustをよくご存知ない方向けに補足しておきます。

Rustは明示的なコンパイルが必要な言語です。CとかC++とかと一緒です。
もっとも単純な方法は、Rustコンパイラを直接使用することです。

hello.rs
fn main() {
  println!("hello");
}
$ rustc hello.rs
$ ./hello
hello

これで問題なくコンパイルと実行ができます。
しかし、外部クレート[1]を利用するとなると、このやり方では複雑になりすぎます。

$ rustc main.rs \
    -L dependency=/path/to/dependencies \
    --extern reqwest=/path/to/reqwest/libreqwest.rlib \
    --extern tokio=/path/to/tokio/libtokio.rlib \
    --edition=2018 ...などなど

rustcには外部クレートを指定するオプションが用意されていますが、たとえば規模の大きな外部クレートを複数オプション指定するとなると、複雑なコマンドを何度も実行するはめになるのが容易に想像できます。

これを解決するために、RustにはCargoというビルドツール(兼 パッケージマネージャ)が用意されています。PythonのPoetry、JavaのMaven、RubyのBundlerなどを想像してください。Cargoはそれらに似て、外部クレートの依存を自動的に解決し、ビルドプロセスを簡素化してくれます。

$ cargo build hello.rs

Rustのビルドには普通Cargoを使い、rustcを直接用いることは滅多にありません。

一方で、他のビルドツール同様、Cargoを使用するにはある程度決められたディレクトリ構成(プロジェクト)の中で作業する必要があります。依存する外部クレートをCargo.tomlという設定ファイルに記述する必要もあります。

rust-scriptは、rustcではなくCargoの恩恵を受けたい、けどプロジェクトをつくるのは面倒だ、もっとPythonやJavaScriptみたいにシュッと実行したいんだ、という要求を叶える、ニッチでわがままなツールというわけです。

外部クレートはドキュメンテーションコメントで指定することができます。
shebangの直後に、inner doc comment [2] の表記 //! として記述します。
記述内容は、Cargoでプロジェクトを作成した際につくられるCargo.tomlのうち、外部クレートに関する[dependencies]の部分を、コードブロックで囲んだものです。

#!/usr/bin/env rust-script

//! ```cargo
//! [dependencies]
//! time = "0.1.25"
//! ```
fn main() {
    println!("{}", time::now().rfc822z());
}

やってみる

rust-script公式サイトには、上記のようなtimeを使ったシンプルな例が掲載されていますが、Cargoのおかげで複数の外部クレートが簡単に使えることですし、ちょっぴり複雑な例を試してみたいですね。
お題は非同期HTTPリクエストです。外部クレートとして、HTTPクライアントreqwest、非同期処理のためにtokiofuturesを利用します。

サンプルコードを参考にざっくり書いて、加えてファイルの先頭にrust-script用のコメントを書いてみました。

async-http-requests.rs
#!/usr/bin/env rust-script

//! ```cargo
//! [dependencies]
//! reqwest = "0.11"
//! tokio = { version = "1", features = ["full"] }
//! futures = "0.3.29"
//! ```
use reqwest;
use tokio;
use futures::future;

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let urls = [
        "https://httpbin.org/status/200",
        "https://httpbin.org/status/404",
        "https://httpbin.org/status/500",
    ];

    let requests = urls.iter().map(|&url| {
        println!("Fetching {:?}...", url);
        reqwest::get(url)
    });

    let responses = future::join_all(requests).await;

    for response in responses {
        match response {
            Ok(res) => {
                println!("Response: {:?} {}", res.version(), res.status());
                println!("Headers: {:#?}\n", res.headers());
            },
            Err(e) => eprintln!("Request failed: {}", e),
        }
    }

    Ok(())
}

うごくかな?

$ rust-script async-http-requests.rs
Fetching "https://httpbin.org/status/200"...
Fetching "https://httpbin.org/status/404"...
Fetching "https://httpbin.org/status/500"...
Response: HTTP/1.1 200 OK
Headers: {
    "date": "Tue, 05 Dec 2023 02:46:33 GMT",
    "content-type": "text/html; charset=utf-8",
    "content-length": "0",
    "connection": "keep-alive",
    "server": "gunicorn/19.9.0",
    "access-control-allow-origin": "*",
    "access-control-allow-credentials": "true",
}

Response: HTTP/1.1 404 Not Found
Headers: {
    "date": "Tue, 05 Dec 2023 02:46:33 GMT",
    "content-type": "text/html; charset=utf-8",
    "content-length": "0",
    "connection": "keep-alive",
    "server": "gunicorn/19.9.0",
    "access-control-allow-origin": "*",
    "access-control-allow-credentials": "true",
}

Response: HTTP/1.1 500 Internal Server Error
Headers: {
    "date": "Tue, 05 Dec 2023 02:46:33 GMT",
    "content-type": "text/html; charset=utf-8",
    "content-length": "0",
    "connection": "keep-alive",
    "server": "gunicorn/19.9.0",
    "access-control-allow-origin": "*",
    "access-control-allow-credentials": "true",
}

いいかんじです。
このくらいの規模のコードがシュッと動かせるなら、外部クレートを使ったPoCなんかにいいんじゃないでしょうか。

rust-script、おすすめです。

余談

Rustをスクリプト言語っぽく実行するツールは、別段あたらしいものではありません。
たとえば、rust-scriptのほかにcargo-scriptというツールがあります。
Rust昔ちょっと触ったことあるよ、という方ならご存知かもかもしれませんね。
https://github.com/DanielKeep/cargo-script
rust-scriptはcargo-scriptのforkです。
cargo-scriptは2015年ごろから存在していましたがメンテナンスが滞り、そこから2020年ごろにforkされたのがrust-scriptになります。
rust-scriptは2023年12月現在、メンテナンスが続いています。よかった〜🦀

これからもがんばってほしいですね。

脚注
  1. クレートとはRustにおけるコンパイル単位。ざっくりいうと、外部クレートとはサードパーティライブラリのこと。 ↩︎

  2. コメントを「含む」要素について説明するコメント。他にコメントの直後の要素を説明するouter doc commentがある。 ↩︎

Discussion