rust-script

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

View on GitHub

Overview

With rust-script 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.

Installation

Install or update rust-script using Cargo:

cargo install rust-script

Rust 1.47 or later is required.

Scripts

The primary use for rust-script is for running Rust source files as scripts. For example:

$ echo 'println!("Hello, World!");' > hello.rs
$ rust-script 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 -o/--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.

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

#!/usr/bin/env rust-script
//! 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:

$ rust-script now
Wed, 28 Oct 2020 00:38:45 +0100

Useful command-line arguments:

Executable Scripts

On Unix systems, you can use #!/usr/bin/env rust-script 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 rust-script. This allows you to execute Rust scripts simply by naming them like any other executable or script.

This can be done using the rust-script --install-file-association command. Uninstall the file association with rust-script --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:

$ rust-script -e '1+2'
3
$ rust-script --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):
$ rust-script --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 rust-script to write a quick filter, by specifying a closure to be called for each line read from stdin, like so:

$ cat now.ers | rust-script --loop \
    "let mut n=0; move |l| {n+=1; println!(\"{:>6}: {}\",n,l.trim_right())}"
     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 | rust-script --count --loop \
    "|l,n| println!(\"{:>6}: {}\", n, l.trim_right())"
     1: // cargo-deps: time="0.1.25"
     2: fn main() {
     3:     println!("{}", time::now().rfc822z());
     4: }

Note that, like with expressions, you can specify a custom template for stream filters.

Environment Variables

The following environment variables are provided to scripts by rust-script:

Templates

You can use templates to avoid having to re-specify common code and dependencies. You can find out the directory where templates are store and view a list of your templates by running rust-script --list-templates.

Templates are Rust source files with two placeholders: #{prelude} for the auto-generated prelude (which should be placed at the top of the template), and #{script} for the contents of the script itself.

For example, a minimal expression template that adds a dependency and imports some additional symbols might be:

// cargo-deps: itertools="0.6.2"
#![allow(unused_imports)]
#{prelude}
use std::io::prelude::*;
use std::mem;
use itertools::Itertools;

fn main() {
    let result = {
        #{script}
    };
    println!("{:?}", result);
}

If stored in the templates folder as grabbag.rs, you can use it by passing the name grabbag via the --template option, like so:

$ rust-script -t grabbag -e "mem::size_of::<Box<Read>>()"
16

In addition, there are three built-in templates: expr, loop, and loop-count. These are used for the --expr, --loop, and --loop --count invocation forms. They can be overridden by placing templates with the same name in the template folder.

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.