# Running Little Rust Snippets
## Leaving the Comfort (and Restrictions) of Cargo
Cargo is a good, reliable way to build programs and libraries in Rust with versioned dependencies.
Those of us who have worked with the Wild West practices of C++ development find this particularly soothing,
and it's one of the core strengths of the Rust ecosystem.
However, it's not intended to make running little test programs straightforward - you have to
create a project with all the dependencies you wish to play with, and then edit `src/main.rs` and
do `cargo run`. A useful tip is to create a `src/bin` directory containing your little programs
and then use `cargo run --bin NAME` to run them. But there is a better way; if you have such
a project (say called 'cache') then the following invocation will compile and link
a program against those dependencies (`rustc` is an unusually intelligent compiler)
```
$ rustc -L /path/to/cache/target/debug/deps mytest.rs
```
Of course, you need to manually run `cargo build` on your `cache` project whenever new dependencies
are added, or when the compiler is updated.
The `runner` tool helps to automate this pattern. It also supports _snippets_, which
are little 'scripts' formatted like Rust documentation examples.
```
$ cat print.rs
println!("Hello, World!");
$ runner print.rs
Hello, World!
```
The snippet file will be found on `RUNNER_PATH` if defined, e.g. `.:/home/me/myscripts`.
This follows basically the same rules as the doc-test snippets you find in Rust
documentation, so `runner` allows you to copy those snippets into an editor
and directly run them.
You can use `?` in snippets instead of the ubiquitous and awful `unwrap`, since the boilerplate
encloses code in a function that returns `Result<(),Box<Error+Sync+Send>>` which is compatible with
any error return.
A special variable `args` is available containing any arguments passed to the program:
```
$ cat hello.rs
println!("hello {}",args[1]);
$ runner hello.rs dolly
hello dolly
```
You can even - on Unix platforms - add a 'shebang' line to invoke runner (but will still need an .rs extension).
```
$ cat hello.rs
#!/usr/bin/env runner
println!("Hello, World!");
$ chmod a+x hello.rs
$ ./hello.rs
Hello, World!
```
> You can choose for these scripts not to be re-compiled each time with the `-R` flag. Then the
> shebang line becomes `#!/usr/bin/env -S runner -R` and `runner` will compare the script timestamp with
> the executable.
`runner` adds the necessary boilerplate and creates a proper Rust program in `~/.cargo/.runner/bin`,
prefixed with a prelude, which is initially:
```rust
#![allow(unused_imports)]
#![allow(unused_variables)]
#![allow(dead_code)]
#![allow(unused_macros)]
use std::{fs, io, env};
use std::fs::File;
use std::io::prelude::*;
use std::path::{PathBuf, Path};
use std::collections::HashMap;
use std::time::Duration;
use std::thread;
macro_rules! debug {
($x:expr) => {
println!("{} = {:?}",stringify!($x),$x);
}
}
```
After first invocation of `runner`, this is found in `~/.cargo/.runner/prelude`;
you can edit it later with `runner --edit-prelude`.
If a file `env.rs` exists on `RUNNER_PATH` then its contents will be appended to the program prelude.
This is useful to share constants and small functions among all your scripts.
`debug!` saves typing: `debug!(my_var)` is equivalent to `println!("my_var = {:?}",my_var)`.
`runner` will also do some massaging of `rustc` errors.
They are usually very good, but involve fully qualified type names.
It reduces `std::` references to something simpler.
This is a snippet which a Java programmer would find easy to write - declare that type explicitly,
and assume that the important verb is "set":
```
$ cat testm.rs
let mut map: HashMap<String,String> = HashMap::new();
map.set("hello","dolly");
$ runner testm.rs
error[E0599]: no method named `set` found for type `HashMap<String, String>` in the current scope
--> /home/steve/.cargo/.runner/bin/testm.rs:24:9
|
|
= help: did you mean `get`?
```
Since we are being very _informal_ with Rust here, it's appropriate that we don't wish the type spelled
out in full glory (as you can see by running with `-S`):
`std::collections::HashMap<std::string::String, std::string::String>`.
## Adding External Crates
As you can see, `runner` is very much about playing with small code snippets. By
default it links the snippet _dynamically_ which is significantly faster.
The static option is much more convenient. You can easily create a static
cache with some common crates:
```
$ runner --add "time json regex"
```
You can add as many crates if you like - number of available dependencies doesn't
slow down the linker. Thereafter, you may refer to these crates in snippets. Note that
by default, `runner` uses 2024 edition since 0.7.0. If you want a particular version, use 'foo=1.0'
notation, and 'foo=1.0/f1,f2' to specify a version plus features.
```rust
// json.rs
use json;
let parsed = json::parse(r#"
{
"code": 200,
"success": true,
"payload": {
"features": [
"awesome",
"easyAPI",
"lowLearningCurve"
]
}
}
"#) ?;
println!("{}", parsed);
```
And then build statically and run (any extra arguments are passed to the program.)
```json
$ runner -s json.rs
{
"code": 200,
"success": true,
"payload": {
"features": [
"awesome",
"easyAPI",
"lowLearningCurve"
]
}
}
```
A convenient feature is "argument lines" - if the first line of `json.rs` was
```
//: -s
```
then any `runner` arguments specified after "//:" will be merged in with the command-line arguments.
It is now possible to simply invoke using `runner json.rs`. It's better to keep any special build
instructions in the file itself, and it means that an editor run action bound to `runner FILE` can be
made to work in all cases.
`runner` provides various utilities for managing the static cache.
You can say `runner --edit` to edit the static cache `Cargo.toml`, and `runner --build` to
rebuild the cache afterwards. `runner --update` will update all the dependencies in the
cache, and `runner --update package` will update a _particular_ package - follow this
with `build` as before. (for `--edit` to work on Posix then define `VISUAL` or `EDITOR`
in environment.)
The cache is built in release mode, since the compilation time is not much affected.
Documentation is also built
for the cache, and `runner --doc` will open that documentation in the browser. (It's
always nice to have local docs, especially in bandwidth-starved situations. If you
are in WSL2 install the `wslu` package so the docs will open on the host.)
If you want docs for a specific crate `NAME`, then `runner --doc NAME` will work.
Remember that the Rust documentation generated has a fast offline searchable
index!
The `--crates` command also has an optional argument; without arguments it lists all
he crates known to `runner`, with their versions. With a name, it uses an exact match:
```
$ runner --crates yansi
yansi = "0.3.4"
```
You may provide a number of crate names here; if `--verbose` (`-v`) is specified
then the dependencies of these crates are also listed.
The `-c` flag only compiles the program or snippet, and copies it to `~/.cargo/bin`.
`-r` only runs the program, which must have previously been compiled, either
explicitly with `-c` or implicitly with default operation. `-R` will rerun if changed,
which is more convenient.
Plain Rust source files (which already have `fn main`) are of course supported, but you
will need explicit `extern crate <crate>` statements to bring in any crates from the static cache.
A useful trick - if you want to look at the `Cargo.toml` of a crate in the static cache,
to find out dependencies and features, then this command will open it for you:
```
favorite-editor $(runner -P some-crate)/Cargo.toml
```
## Rust on the Command-line
There are a few Perl-inspired features. The `-e` flag compiles and evaluates an
_expression_. You can use it as an unusually strict desktop calculator:
```
$ runner -e "10 + 20*4.5"
error[E0277]: the trait bound `{integer}: Mul<{float}>` is not satisfied
--> temp/tmp.rs:20:22
|
will give you its local documentation.
However, it can't be compiled directly, for the reason that `use std::fs` is already in the runner prelude.
So we need to say:
```
$ runner -s --no-prelude filetime.rs
1506778536.945440909s
1506778536
945440909
1506778536
```
Or if you're in a hurry: `runner -sN filetime.rs`.
As always, can always put these arguments in a first comment like so "//: -sN".
## Dymamic Compilation of Crates
It would be good to provide such an experience for the dynamic-link case, since
it is faster. There is in fact a dynamic cache as well but support for linking
against external crates dynamically is very basic. It works fine for crates that
don't have any external depdendencies, e.g. this creates a `libjson.so` in the
dynamic cache:
```
$ runner -C json
```
And then you can run the `json.rs` example without `-s`.
The `--compile` action takes three kinds of arguments:
- a crate name that is already loaded and known to Cargo
- a Cargo directory
- a Rust source file - the crate name is the file name without extension.
Dynamic linking is not a priority for
Rust tooling at the moment. So although it is possible to write a wrapper crate that exposes e.g. `regex`
as a shared library, it is still rather unstable.