Alex Balgavy

Just some stuff about me.

Here's my dotfiles repository.

Check out my blog.

My keys: PGP, SSH

My crypto wallets (BTC, XMR)


What links here:

Rust programming basics

rustc is compiler, cargo is build tool, rustfmt is formatter. Execution starts from main function.

To turn off compilation warnings (only temporarily, otherwise keep them on!):

#[allow(unused_variables, dead_code, ...)]

Variables & constants

mutable have to be indicated with mut, types can be inferred and can be suffixed to numbers:

let x: i32 = 1i32; // immutable
let mut mutable = 1; // mutable, inferred type
const MYCONST = 0x42; // compile-time constant
static MYGLOB: u32 = 0x42; // global variable

Strings:

// Creating Strings from &str
let s: &str = "string literal";
let s: String = "heap-allocated string".to_string();

// Convert String to &str:
let s_ref: &str = s.as_str();
let s_ref: &str = &*s;

Printing:

println!("{} {:?}", s, s); // with `:?`, print debug-style

Arrays:

let fixed_size: [i32; 4] = [1, 2, 3, 4];
let mut dynamic_size: Vec<i32> = vec![5, 6, 7, 8];
dynamic_size.push(fixed_size[1]); // can add elements like this, indexing with [] from 0

// Mapping over an array
let list = [1,2,3];
let result: Vec<i64> = list.iter().map(|x| x*2).collect(); // without collect(), returns iterator. then you can run .next(), .all(), etc.

Slices: immutable views into something

let s = "Hello".to_string();
let mut dynamic_size: Vec<i32> = vec![5, 6, 7, 8];

let s_slice: &str = &s[..]; // string slices are immutable 'views' into another string
let arr_slice: &[i32] = &dynamic_size[0..2]; // same for array slices, inclusive with [0..=2]

Tuples: fixed size set of values, possibly different types

let x: (I32, &str, f64) = (1, "answer", 42.69);
let (a, b, c) = x; // you can destructure like this
println!("{}", x.1); // and index with ., zero-based

Structs: like in C

// named fields
struct Point {
    x: i32,
    y: i32
}
// unnamed fields ("tuple struct")
struct Point2(i32, i32);

// generic struct
struct Foo<T> { bar: T }

// initializing
let origin: Point = Point { x: 0, y: 0 };
let origin2: Point2(0, 0);
let foo = Foo { bar: "hello".to_string() };

// accessing
println!("{} {} {}", origin.x, origin.y, foo.bar);

Enums:

// C-like
enum Direction { Left, Right, Up, Down }
let up = Direction::Up;

// Containing values
enum OptionalI32 { Some(i32), None }
let two = OptionalI32::Some(2);
let nothing = OptionalI32::None;

// Generic
enum SMLOption<T> { Some(T), None }

Options:

let maybestr: Option<&str> = Some("hello");
let nonumber: Option<u32> = None;
assert!(maybestr.is_some() && nonumber.is_none());
println!("str is {}", maybestr.unwrap());
println!("nonumber will not print here {}", nonumber.expect("Can't unwrap a None.")); // this panics

// One way to access:
if let Some(s) = maybestr {
    dbg!(s);
}
// You can also use pattern matching, see below.

Type aliases

You can define a type alias like in C:

type MyType = u32;

Functions:

fn add(a: i32, b: i32) -> i32 {
    a + b                       // no semicolon means implicit return
}
fn main() {
    println!("{}", add(2, 3));
}

Methods:

pub struct Foo { val: i32 }
// can also be generic, then you do `impl<T> Foo<T>`
impl Foo {
    // constructor
    pub fn new() -> Self {
        Foo { val: 0 }
    }

    // inherent function, called with `::`
    pub fn sayhello() {
        println!("Hello");
    }

    // private method
    fn set_even(&mut self, val: i32) {
        self.val = val;
    }

    // public setter method
    pub fn set(&mut self, val: i32) {
        if val % 2 == 0 {
            self.set_even(val);
        }
        else {
            eprintln!("{} not even, not setting", val);
        }
    }

    // public gtter method
    pub fn get(&self) -> i32 {
        self.val
    }

    // consume: move the value into the function to prevent reuse in following code
    pub fn consume(self) -> i32 {
        self.val
    }
}

Foo::sayhello();
let foo = Foo::new();
foo.set(4);
dbg!(foo.consume());
dbg!(foo.get()); // Error: foo was moved in the consume call

Ownership:

each value has one owner. Simple types and tuples that only contain simple types implement Copy (i.e. if passed into function, the function doesn’t take ownership). For example:

fn copies(n: i32) {
    println!("inside copies: {}", n);
}

fn takes_ownership(s: String) {
    println!("inside: {}", s);
}

fn uses_reference(s: &String) {
    println!("inside: {}", s);
    s.push_str(", world");      // fails! reference is not mutable
}

fn uses_mut_reference(s: &mut String) {
    s.push_str(", world");
}

fn main() {
    let x = 42;                  // "x" owns the int value
    copies(x);                   // value copied
    println!("outside: {}", x);  // OK, because i32 implements Copy

    let s = "hello".to_string(); // "s" owns the string value
    takes_ownership(s);         // value moved here, owned inside function
    println!("outside: {}", s); // "s" is now invalid! because borrowed after move -- function parameter has ownership.

    let mut s2 = "hello".to_string(); // "s2" owns the string value
    uses_reference(&s2);          // the function borrows the value, but s2 retains ownership
    println!("outside: {}", s2);  // so this is valid
    uses_mut_reference(&mut s2);  // this lets us change the value
    println!("after change: {}", s2);

    // This fails. You can't have simultaneously in the same scope: multiple mutable refs, mutable + immutable refs.
    let r1 = &mut s2;
    let r2 = &mut s2;
    // to fix, you can put r2 into its own scope with curly braces.
}

Memory

All values are stack-allocated by default. To allocate on the heap, use a Box:

let mut foo: Box<i32> = Box::new(3);
println!("{}", foo);
*foo = 42;
println!("{}", foo);

Traits: like interfaces/APIs/whatever

struct Foo {
    a: i32,
    b: i32
}

trait FooPredicates {
    fn all_even(&self) -> bool;
    fn some_odd(&self) -> bool;
}

impl FooPredicates for Foo {
    fn all_even(&self) -> bool {
        (self.a % 2 == 0) && (self.b % 2 == 0)
    }
    fn some_odd(&self) -> bool {
        !self.all_even()
    }
}

fn main() {
    let mut f = Foo { a: 2, b: 4 };
    println!("all_even {}, some_odd {}", f.all_even(), f.some_odd());
    f.b = 3;
    println!("all_even {}, some_odd {}", f.all_even(), f.some_odd());
    f.a = 5;
    println!("all_even {}, some_odd {}", f.all_even(), f.some_odd());
}

You can automatically implement traits with derive, e.g. #[derive(Copy)], if all members of the struct also implement the trait.

Pattern matching:

struct Foo {
    a: i32,
    b: Option<i32> // from stdlib
}
fn matcher(foo: &Foo) {
    match foo {
        Foo { a: n, b: Some(m) } if n == m => println!("both are {}", n),
        Foo { a: _, b: Some(m) } => println!("there is some {}", m),
        Foo { a: n, b: None } => println!("none, only a is {}", n)
    }
}
fn main() {
    let mut foo = Foo { a: 2, b: Some(42) };
    matcher(&foo);
    foo.b = None;
    matcher(&foo);
    foo.b = Some(foo.a);
    matcher(&foo);
}

Error handling:

use std::fs;

fn main() {
    match fs::read("nonexistentfile") {
        Ok(_) => println!("This won't display."),
        Err(e) => {
            eprintln!("We know the file doesn't exist!");
            panic!("{}", e);
        }
    }
}

// You can also not handle, with .unwrap(), .expect(error message), etc.
// .expect() gives you the error message when there's an error.
// Both panic in cases where an Option is None, or Result is Err!
// Using question mark either gives you the result, or returns an error.

Loops:

// iterate over array
for i in &[1,2,3] {
    println!("{}", i);
}
// iterate over range (exclusive highest number)
for i in 0..10 {
    print!("{} ", i);
}; println!("");

// iterate with index
for (i, v) in (10..20).enumerate() {
    println!("{}: {}", i, v);
}

// while loop
while 1 == 1 {
    println!("everything ok");
    break
}
// and `loop { ... }` is infinite

You can also break-with-value:

use rand::Rng;
fn main() {
    let mut rng = rand::thread_rng();
    let mut attempt = 0;
    let res = loop {
        attempt += 1;
        let num: u32 = rng.gen();
        if num % 2 == 0 {
            break num;
        }
    };
    println!("got even number: {} (attempt {})", res, attempt);
}

Separating code with modules

this blog post does a better job explaining than I could

Reading a TOML config file:

// Add to Cargo.toml:
// [dependencies]
//
// Given a file like this called config.toml:
//
// [config]
// answer = 42
// other = [{name = "something"}]
//

use serde::Deserialize;
use std::fs;

// Set up config structure
#[derive(Deserialize)]
struct ConfigData {
    config: Config
}

#[derive(Deserialize)]
struct Config {
    answer: i32,
    other: Vec<Item>
}

#[derive(Deserialize)]
struct Item {
    name: String
}

fn main() {
    // Read file (need to do error handling)
    let contents = fs::read_to_string("config.toml").unwrap();
    // Parse config (need to do error handling)
    let data: ConfigData = toml::from_str(&contents).unwrap();
    println!("answer is {}, and name is {}", data.config.answer, data.config.other[0].name);
}

Spawning external programs:

use std::process::Command

// Interactive
let _ = Command::new("less")
    .env("LESS", "")
    .args(&["-RiX", "/etc/profile"])
    .spawn()
    .expect("Less didn't work.")
    .wait();

// Output to /dev/null
Command::new("ls")
    .arg("-l")
    .stdout(std::process::Stdio::null())
    .spawn()
    .expect("ls couldn't ls.");

// Collect output
use std::io::{self,Write};
fn main() {
    let output = Command::new("ls")
        .arg("-l")
        .output()
        .expect("ls couldn't ls.");
    println!("status: {}", output.status);
    io::stdout().write_all(&output.stdout).unwrap();
}

Higher-order functions:

// returns a mapped copy
fn mymap<T, F>(v: Vec<T>, f: F) -> Vec<T>
where
    F: Fn(T) -> T,
{
    let mut res = vec![];
    for i in v {
        res.push(f(i));
    }
    return res;
}

// modifies the existing vector in-place
fn mymapinplace<T, F>(v: &mut Vec<T>, f: F)
where
    T: Copy,
    F: Fn(T) -> T,
{
    for i in v {
        *i = f(*i)
    }
}

fn main() {
    let v = mymap(vec![1, 2, 3, 4], |x: i32| x * 2);
    dbg!(&v);

    let mut w = vec![2.42, 3.42, 4.42];
    mymapinplace(&mut w, |x: f32| x.powf(2f32));
    dbg!(&w);
}

You can also return functions:

fn returns_fn() -> impl Fn(i32) -> i32 {
    |x| x*2
}
fn main() {
    let x = 42;
    let y = returns_fn()(x);
    dbg!(y);
}

Good articles to read