Skip to content

Instantly share code, notes, and snippets.

@lambda-mike
Last active August 5, 2020 09:14
Show Gist options
  • Save lambda-mike/c4291ea944f2b4aeccb8ffe4b701cdfb to your computer and use it in GitHub Desktop.
Save lambda-mike/c4291ea944f2b4aeccb8ffe4b701cdfb to your computer and use it in GitHub Desktop.

Rust

Cargo

cargo new --bin myproj

cargo new --lib mylib

cargo build --release

cargo run

rustup doc - offline docs in the browser

You can add additional binaries by placing them in a bin/ directory.

The build script is simply another Rust file that will be compiled and invoked prior to compiling anything else in the package.

Error handling

  • quick-error
  • error-chain
  • failure

Self-referencing data structures

rental or owning-ref crates

Numbers

let x = 32i64

let pi = 3.141592;
println!("Pi is roughly {:.3}", pi);

"pretty printing" with {:#?}

Variables can be overwritten with shadowing.

0.000_001 and 1_000 are both valid.

println!("one element tuple: {:?}", (5u32,));

let integer = decimal as u8; // Explicit cast

let parsed: i32 = "5".parse().unwrap();
let turbo_parsed = "10".parse::<i32>().unwrap();

Cuctom Types

// A unit struct
struct Unit;

// A tuple struct
struct Pair(i32, f32);

// A struct with two fields
struct Point {
    x: f32,
    y: f32,
}

let bottom_right = Point { x: 5.2, ..point };

Enums

use crate::Status::{Poor, Rich};
use crate::Work::*;
//...
enum Color {
    Red = 0xff0000,
    Green = 0x00ff00,
    Blue = 0x0000ff,
}
println!("violets are #{:06x}", Color::Blue as i32);
//...
enum List {
    // Cons: Tuple struct that wraps an element and a pointer to the next node
    Cons(u32, Box<List>),
    // Nil: A node that signifies the end of the linked list
    Nil,
}

format! is similar to print!, but returns a heap allocated string instead of printing to the console

Control flow

'outer: loop {
//...
break 'outer;

Return value from the loop: break counter * 2;

Inclusive range: for n in 1..=100 {

match

match foo {
    Foo { y, .. } => println!("y = {}, we don't care about x", y),
match pair {
        (x, y) if x == y => println!("These are twins"),
        //...
match age() {
    n @ 1  ..= 12 => println!("I'm a child of age {:?}", n),
if let Foo::Bar = a {

Functions

Closures can capture variables:

  • by reference: &T
  • by mutable reference: &mut T
  • by value: T

Using move before vertical pipes forces closure to take ownership of captured variables

When taking a closure as an input parameter, the closure's complete type must be annotated using one of a few traits. In order of decreasing restriction, they are:

  • Fn: the closure captures by reference (&T)
  • FnMut: the closure captures by mutable reference (&mut T)
  • FnOnce: the closure captures by value (T)
// A non-copy type.
// `to_owned` creates owned data from borrowed one
let mut farewell = "goodbye".to_owned();
fn create_fn() -> impl Fn() {
    let text = "Fn".to_owned();
    move || println!("This is a: {}", text)
}

Diverging functions never return. They are marked using !, which is an empty type.

Modules

pub(in crate::my_mod) fn public_function_in_my_mod() {

Functions declared using pub(super) syntax are only visible within the parent module.

Crates

A crate can be compiled into a binary or into a library. By default, rustc will produce a binary from a crate. This behavior can be overridden by passing the --crate-type flag to lib.

Libraries get prefixed with "lib", and by default they get named after their crate file, but this default name can be overridden by passing the --crate-name option to rustc or by using the crate_name attribute.

rustc executable.rs --extern rary=library.rlib

Attributes

When attributes apply to a whole crate, their syntax is #![crate_attribute], and when they apply to a module or item, the syntax is #[item_attribute] (notice the missing bang !).

Generics

// Explicitly specified type parameter `char` to `generic()`.
generic::<char>(SGen('a'));

Multiple bounds: fn compare_prints<T: Debug + Display>(t: &T) {

impl <A, D> MyTrait<A, D> for YourType where
A: TraitB + TraitC,
D: TraitE + TraitF {}

Struct std::marker::PhantomData

Zero-sized type used to mark things that "act like" they own a T.

Scoping

let _box1 = Box::new(3i32);

Those assignments are equal:

let ref ref_c1 = c;
let ref_c2 = &c;
let (_, ref mut last) = mutable_tuple;

Lifetime

T: Trait + 'a: Type T must implement trait Trait and all references in T must outlive 'a.

<'a: 'b, 'b> reads as lifetime 'a is at least as long as 'b.

Traits

The following is a list of derivable traits:

  • Comparison traits: Eq, PartialEq, Ord, PartialOrd.
  • Clone, to create T from &T via a copy.
  • Copy, to give a type 'copy semantics' instead of 'move semantics'.
  • Hash, to compute a hash from &T.
  • Default, to create an empty instance of a data type.
  • Debug, to format a value using the {:?} formatter.

Interesting destructuring: let &Inches(inches) = self;

if your function returns a pointer-to-trait-on-heap in this way, you need to write the return type with the dyn keyword, e.g. Box<dyn Animal>.

Simplifying type signatures: -> impl Iterator<Item=i32> {

/ CompSciStudent (computer science student) is a supertrait of both Programmer 
// and Student. Implementing CompSciStudent requires you to impl both subtraits.
trait CompSciStudent: Programmer + Student {
//...

Disambiguating calls to method named identically in different traits:

let age = <Form as AgeWidget>::get(&form);

macro_rules!

The arguments of a macro are prefixed by a dollar sign $ and type annotated with a designator.

The stringify! macro converts an ident into a string.

The expr designator is used for expressions.

https://doc.rust-lang.org/reference/macros-by-example.html

Macros can be overloaded to accept different combinations of arguments.

Errors

? means unwrap or return Err(From::from(err)).

filter_map calls a function and filters out the results that are None.

Result implements FromIter so that a vector of results (Vec<Result<T, E>>) can be turned into a result with a vector (Result<Vec<T>, E>). Once an Result::Err is found, the iteration will terminate.

Std lib

// Iterators can be collected into vectors
let collected_iterator: Vec<i32> = (0..10).collect();

Strings

A String is stored as a vector of bytes (Vec<u8>), but guaranteed to always be a valid UTF-8 sequence. String is heap allocated, growable and not null terminated.

&str is a slice (&[u8]) that always points to a valid UTF-8 sequence, and can be used to view into a String, just like &[T] is a view into Vec<T>.

let byte_escape = "I'm writing \x52\x75\x73\x74!";
println!("What are you doing\x3F (\\x3F means ?) {}", byte_escape);
// ...or Unicode code points.
let unicode_codepoint = "\u{211D}";
let raw_str = r"Escapes don't work here: \x3F \u{211D}";
println!("{}", raw_str);
// If you need quotes in a raw string, add a pair of #s
let quotes = r#"And then I said: "There is no escape!""#;
let bytestring: &[u8; 21] = b"this is a byte string";
let raw_bytestring = br"\u{211D} is not escaped here";

Hash Maps

You can easily implement Eq and Hash for a custom type with just one line: #[derive(PartialEq, Eq, Hash)]

Threads

Rust provides a mechanism for spawning native OS threads via the spawn function, the argument of this function is a moving closure.

Files

Read lines efficiently: https://doc.rust-lang.org/rust-by-example/std_misc/file/read_lines.html

Rust provides a Foreign Function Interface (FFI) to C libraries. Foreign functions must be declared inside an extern block annotated with a #[link] attribute containing the name of the foreign library.

Testing

#[cfg(test)]
mod tests {
    // Note this useful idiom: importing names from outer (for mod tests) scope.
    use super::*;

    #[test]
    fn test_add() {
        assert_eq!(add(1, 2), 3);
    }
}

Unit tests can return Result<()>, which lets you use ? in them.

Test for panics: #[should_panic(expected = "Divide result is zero")]

cargo test test_any_panic

Tests can be marked with the ,#[ignore], attribute to exclude some tests. Or to run them with command cargo test -- --ignored

Syntax for panicing in doccomments: /// ```rust,should_panic

/// # // hidden lines start with # symbol, but they're still compileable!

Cargo looks for integration tests in tests directory next to src.

Each Rust source file in tests directory is compiled as a separate crate.

Dev dependencies are added to Cargo.toml in the [dev-dependencies] section.

Unsafe

let raw_p: *const u32 = &10;

Raw identifiers

fn main() {
    foo::r#try();
}

Debugging

https://www.valgrind.org/info

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment