# 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!
```
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 (I bind 'run' for Rust projects to `runner ...` in
my favourite editor.)
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:
```
$ cat hello
#!/usr/bin/env runner
println!("Hello, World!");
$ ./hello
Hello, World!
```
`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`.
`debug!` saves typing: `debug!(my_var)` is equivalent to `println!("my_var = {:?}",my_var)`.
As an experimental feature, `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 2018 edition since 0.4.0.
```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 new 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.
The cache is built for both debug and release mode,
so using `-sO` you can build snippets in release mode. 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 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.
Plain Rust source files (which already have `fn main`) are of course supported, but you
will need the `--extern` (`-x`) flag to bring in any external crates from the static cache.
A useful trick - if you want to look at the `Cargo.toml` of an already downloaded crate
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 we have to build more elaborate libraries without the
help of Cargo. (The following assumes that you have already brought in `regex` for a Cargo project,
so that the Cargo cache is populated, e.g. with `runner --add regex`)
```
runner -C memchr
runner -C aho-corasick
runner -C utf8-ranges
runner -C lazy_static
runner -xlazy_static -C thread_local
runner -C regex-syntax
runner -C regex
```
This script drives home how tremendously irritating life in Rust would be without Cargo.
We have to track the dependencies, ensure that the correct default features are enabled in the
compilation, and special-case crates which directly link to `libc`.
However, the results feel worthwhile. Compiling the first `regex` documented example:
```rust
use regex::Regex;
let re = Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
assert!(re.is_match("2014-01-01"));
```
With a static build (`-s`) I get 0.56s on this machine, and 0.25s with dynamic linking.
There are limitations to dynamic linking currently - crates which are "no std"
(and don't provide a feature to turn this off) cannot be compiled. Also, remember
that all invocations of `runner -C` end up with shared libraries placed in one
directory called the 'dynamic cache' - there can only be one crate called 'libs'
for example.