Rust

Image of Author
March 13, 2023 (last updated September 16, 2024)

Resources

https://cheats.rs/

Getting Started with a Rust CLI

Getting Started with a Rust CLI

Strings

Interpolating strings

If you want to interpolate a cli output println!("{}", "hello"). If you want to compose a string and interpolate some values you can use a combinations of String::push_str and the format! macro.

let s = String::from("starting");
s.push_str("continuing");
let end = format!("the end{}", "!");
s.push_str(end);

String and str

StackOverflow answer on the difference between String and &str

Think of Strings like Vecs. It's a dynamic heap string type.

Derived traits

Debug is an important derived trait for structs. There are a few others as well. You add a derived trait via the derive attribute.

#[derive(Debug, Clone)]
struct User {
  age: i8,
  name: String
}

Passing args to commands in cli apps

From std::process::Command docs:

Note that the argument is not passed through a shell, but given literally to the program. This means that shell syntax like quotes, escaped characters, word splitting, glob patterns, substitution, etc. have no effect.

If you don't pay much attention to how shells work, this will trip you up. A shell like bash, zsh, fish, etc., will interpret what you type in order to pass valid parameters to binaries. For example, when you type ls ~ it turns ~ into /Users/<username> or where ever $HOME is, etc.

Hopefully now the Rust quote above makes more sense. If you try to run a command that requires shell expansion, substitution, etc., it will not work. This has gotten me a few times. (1) I've tried to use ~ in a command. (2) I've tried to use >> in a command.

From and Into

The Rust by Example chapter on From and Into is very short and explains it perfectly, assuming you already know what traits are (if you don't, it has a chapter on traits, too).

I want to emphasise one thing that confused me early on, x.into(y) is automatically generated from Y::from(x). from and into are semantically the same function from different perspectives. The only problem here is that into can turn a type into any other type that has defined a from for that type. This means you often need to explicitly type the param you are "turning into" so the compiler knows which from to call. This is easier to demonstrate with code:

struct X {}
struct Y {}
struct Z {}

impl From<X> for Y {
    fn from(value: X) -> Self {
        Y {}
    }
}

impl From<X> for Z {
    fn from(value: X) -> Self {
       Z {}
    }
}

fn demo() {
    let x1 = X {};
    // will error without type
    // because it could also be type `Z`
    let y: Y = x1.into();
    let x2 = X {};
    // same here, could be type `Y`
    let z: Z = x2.into();
}

The Prelude Pattern

From the std::prelude module docs:

The prelude is the list of things that Rust automatically imports into every Rust program. It’s kept as small as possible, and is focused on things, particularly traits, which are used in almost every single Rust program.

This "prelude pattern" is used elsewhere within the standrard library, and libraries have taken to copying it as well.

The difference between ‘the prelude’ and these other preludes is that they are not automatically use’d, and must be imported manually. This is still easier than importing all of their constituent components.

For example, std::io::prelude requires import:

use std::io::prelude::*;

For an example outside std, the testing library assert_fs uses the prelude pattern to import various testing utilities:

use assert_fs::prelude::*;
use predicates::prelude::*;

Setup with rustup

rustup is both the recommended and only official installation method. Like other areas of the Rust ecosystem, there is a rustup book

./rust-toolchain is read by rustup to control the toolchain

rustup help doc will show you all the docs you can download and view locally.

rustup doc --book will render the amazing "Rust book" locally in the default browser.

Once you have a local version of rust you can start a new project via cargo init, etc