runrust

Run Rust files and expressions as scripts without any setup or compilation step.

View on GitHub

Overview

With runrust Rust files and expressions can be executed just like a shell or Python script. Features include:

You can get an overview of the available options using the --help flag.

News

See the changelog for information about releases and changes.

Installation

Install or update runrust using Cargo:

cargo install runrust

Rust 1.64 or later is required.

Distro Packages

Arch Linux

runrust can be installed from the extra repository:

pacman -S runrust

Scripts

The primary use for runrust is for running Rust source files as scripts. For example:

$ echo 'println!("Hello, World!");' > hello.rs
$ runrust hello.rs
Hello, World!

Under the hood, a Cargo project will be generated and built (with the Cargo output hidden unless compilation fails or the -c/--cargo-output option is used). The first invocation of the script will be slower as the script is compiled - subsequent invocations of unmodified scripts will be fast as the built executable is cached.

As seen from the above example, using a fn main() {} function is not required. If not present, the script file will be wrapped in a fn main() { ... } block.

runrust will look for embedded dependency and manifest information in the script as shown by the below two equivalent now.rs variants:

#!/usr/bin/env runrust
//! This is a regular crate doc comment, but it also contains a partial
//! Cargo manifest.  Note the use of a *fenced* code block, and the
//! `cargo` "language".
//!
//! ```cargo
//! [dependencies]
//! time = "0.1.25"
//! ```
fn main() {
    println!("{}", time::now().rfc822z());
}
// cargo-deps: time="0.1.25"
// You can also leave off the version number, in which case, it's assumed
// to be "*".  Also, the `cargo-deps` comment *must* be a single-line
// comment, and it *must* be the first thing in the file, after the
// shebang.
// Multiple dependencies should be separated by commas:
// cargo-deps: time="0.1.25", libc="0.2.5"
fn main() {
    println!("{}", time::now().rfc822z());
}

The output from running one of the above scripts may look something like:

$ runrust now
Wed, 28 Oct 2020 00:38:45 +0100

Useful command-line arguments:

Executable Scripts

On Unix systems, you can use #!/usr/bin/env runrust as a shebang line in a Rust script. This will allow you to execute a script files (which don’t need to have the .rs file extension) directly.

If you are using Windows, you can associate the .ers extension (executable Rust - a renamed .rs file) with runrust. This allows you to execute Rust scripts simply by naming them like any other executable or script.

This can be done using the runrust --install-file-association command. Uninstall the file association with runrust --uninstall-file-association.

If you want to make a script usable across platforms, use both a shebang line and give the file a .ers file extension.

Expressions

Using the -e/--expr option a Rust expression can be evaluated directly, with dependencies (if any) added using -d/--dep:

$ runrust -e '1+2'
3
$ runrust --dep time --expr "time::OffsetDateTime::now_utc().format(time::Format::Rfc3339).to_string()"`
"2020-10-28T11:42:10+00:00"
$ # Use a specific version of the time crate (instead of default latest):
$ runrust --dep time=0.1.38 -e "time::now().rfc822z().to_string()"
"2020-10-28T11:42:10+00:00"

The code given is embedded into a block expression, evaluated, and printed out using the Debug formatter (i.e. {:?}).

Filters

You can use runrust to write a quick filter, by specifying a closure to be called for each line read from stdin, like so:

$ cat now.ers | runrust --loop \
    "let mut n=0; move |l| {n+=1; println!(\"{:>6}: {}\",n,l.trim_end())}"
     1: // cargo-deps: time="0.1.25"
     3: fn main() {
     4:     println!("{}", time::now().rfc822z());
     5: }

You can achieve a similar effect to the above by using the --count flag, which causes the line number to be passed as a second argument to your closure:

$ cat now.ers | runrust --count --loop \
    "|l,n| println!(\"{:>6}: {}\", n, l.trim_end())"
     1: // cargo-deps: time="0.1.25"
     2: fn main() {
     3:     println!("{}", time::now().rfc822z());
     4: }

Environment Variables

The following environment variables are provided to scripts by runrust:

Troubleshooting

Please report all issues on the GitHub issue tracker.

If relevant, run with the RUST_LOG=rust_script=trace environment variable set to see verbose log output and attach that output to an issue.