Resources
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 String
s like Vec
s. 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