Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

C

// Guess the number game
#include <stdio.h>
#include <stdlib.h>

int generate_secret_number(int min, int max) {
    return (rand() % (max - min + 1)) + min;
}

int main() {
    printf("Guess the number!\n");

    int secret_number = generate_secret_number(1, 100);

    while (1) {
        printf("Please input your guess: ");

        int guess;
        scanf("%d", &guess);

        printf("You guessed: %d\n", guess);

        if (guess < secret_number) {
            printf("Too small!\n");
        } else if (guess > secret_number) {
            printf("Too big!\n");
        } else {
            printf("You win!\n");
            break;
        }
    }

    return 0;
}

Types

  • Primitives: int, char, float, double
  • Constants: const
  • Modifiers: signed, unsigned, short, long
  • Casting: (int) 3.14
  • Array: int arr[5];

Operators

  • Arithmetic: +, -, *, /, %
  • Relational: ==, !=, <, <=, >, >=
  • Logical: &&, ||, !
  • Bitwise: &, |, ^, <<, >>
  • Assignment: =, +=, -=, *=, /=, %=
  • Increment/Decrement: ++, --
  • Ternary: condition ? expr1 : expr2

Flow Control

// if-else
if (condition) { /* ... */ }
else if (condition) { /* ... */ }
else { /* ... */ }

// switch-case
switch (enumerable) {
    case variant1: /* ... */ break;
    case variant2: /* ... */ break;
    default: /* ... */
}

// while loop
while (condition) { /* ... */ }

// do-while loop
do { /* ... */ } while (condition);

// for loop
for (int i = 0; i < 10; i++) { /* ... */ }

// break, continue
break;    // exits the loop
continue; // skips the current iteration.

// label & goto
label: /* statement */
goto label; // jumps to the label.

Compound Types

// struct (used to group related data)
struct Point {
    int x;
    int y;
};
struct Point p1 = {0, 0};
p1.x = 3;
p1.y = 4;

// union (only one member can be accessed at a time)
union Data {
    int i;
    float f;
};
union Data data;
// note that accessing `i` after assigned `f` is undefined behavior.
data.i = 10;    // only `i` is accessible now
data.f = 220.5; // only `f` is accessible now

// enum (used to define a set of named integer constants)
enum Color { RED, GREEN, BLUE };
enum Color c = BLUE;
if (c == RED) { /* ... */ }

Pointer

Basic

Pointers are a special type of variable, it stores the memory address of another variable, instead of the value of the variable itself.

int a = 1;

// `&` is the address-of operator
int *p = &a; // `p` is a pointer to `a`

// `*` is the dereference operator
*p = 2; // `a` is now 2

Pointers support pointer arithmetic, which "shifts" the pointer to another location in memory.

int arr[3] = {1, 2, 3};
int *p = arr; // `p` points to `arr[0]`

p++; // now points to `arr[1]`
p++; // now `arr[2]`

Void Pointer

Void pointers are generic pointers that can point to any data type. They are declared using the void keyword.

int a = 1;
float b = 2.0;

void *p;
p = &a; // `p` points to an `int`
p = &b; // `p` points to a `float`

Function Pointer

Pointers can be used to point to functions, which allows for dynamic function calls.

int add(int a, int b) {
    return a + b;
}

// return type is `int`, takes two `int` parameters
int (*func_ptr)(int, int) = add; // `func_ptr` points to `add`

func_ptr(1, 2); // calls `add(1, 2)`

C++

#include <ctime>
#include <iostream>
#include <random>

int generate_secret_number(int min, int max) {
    std::mt19937 rng(time(0));
    return (rng() % (max - min + 1)) + min;
}

int main() {
    std::cout << "Guess the number!" << std::endl;

    int secret_number = generate_secret_number(1, 100);

    while (true) {
        std::cout << "Please input your guess: ";

        int guess;
        std::cin >> guess;

        std::cout << "You guessed: " << guess << std::endl;

        if (guess < secret_number) {
            std::cout << "Too small!" << std::endl;
        } else if (guess > secret_number) {
            std::cout << "Too big!" << std::endl;
        } else {
            std::cout << "You win!" << std::endl;
        break;
      }
    }

    return 0;
}

Refer to C for language basics.

Types

  • New primitive: bool
  • Auto typing: auto x = 5;
  • Copy typing: decltype(x) y = x;
  • Type alias: using Distance = double;
  • Null pointer: int *p = nullptr;
  • Reference: int &r = x;

Namespaces

namespace lyn {
    int number;  // number is a member of namespace lyn (lyn::number)
}

int main() { lyn::number = 32; }

Lambda Functions

auto add = [/* capture */](int a, int b) { return a + b; };
add(1, 2); // => 3

Dynamic Memory

// new and delete
int* p = new int; // allocate memory for an int
*p = 1919;
delete p; // release memory

// new[] and delete[]
int* p = new int[5]; // allocate memory for 5 integers
p[0] = 1919;
delete[] p; // release memory

Object Oriented

Class as Object

Like struct in C, class is a user-defined data type that can hold data members. Unlike struct, class can also hold member functions, known as method.

class Point {
// access specifier: only accessible within the class
private:
    double x;
    double y;

// access specifier: accessible outside the class
public:
    void set(double x, double y) {
        // `this` pointer points to the caller object
        this->x = x;
        this->y = y;
    }

    // const method promises not to modify the object
    double dist(const Point& that) const {
        return sqrt(
            // `this` is optional
            (x - that.x) * (x - that.x) +
            (y - that.y) * (y - that.y)
        );
    }
};  // don't forget the semicolon

Point p;   // create an object of class Point
p.dist();  // access the member function

Constructor & Destructor

A constructor is a special member function that is called when an object is created, used to initialize the object.

A destructor on the other hand is called when an object is destroyed, used to release allocated resources.

class Vector {
private:
    double* elem;
    int sz;

public:
    // default constructor
    Vector() {
        elem = new double[10];
        sz = s;
    }

    // constructor with parameter
    Vector(int s) : // `:` is the initializer list
        elem{new double[s]},
        sz{s}
    {
        for (int i = 0; i < s; ++i) { elem[i] = 0; }
    }

    // destructor
    ~Vector() { delete[] elem; }

    // copy constructor
    Vector(const Vector& a) : elem{new double[a.sz]}, sz{a.sz} {
        for (int i = 0; i < sz; ++i) { elem[i] = a.elem[i]; }
    }

    // move constructor
    Vector(Vector&& a) : elem{a.elem}, sz{a.sz} {
        a.elem = nullptr;
        a.sz = 0;
    }
};

class Point {
private:
    double x;
    double y;

public:
    Point() = default;      // default constructor (compiler generated)
    Point(const Point&) = delete; // no copy constructor (prevent copy)
    Point(Point&&) = delete;    // no move constructor (prevent move)
};

Sharing

friend keyword grants access to private members of a class to another class or function.

// forward declaration
class List;
class Node;

class Node {
    friend class List; // List can access private members of Node
private:
    int data;
    Node* next;
};

class List {
private:
    Node* head;
    Node* tail;
};

static keyword makes a member shared by all instances of the class.

class Counter {
private:
    static int count; // shared by all instances of Counter
public:
    Counter() { ++count; }
    ~Counter() { --count; }
    static int getCount() { return count; }
};

int Counter::count = 0; // initialize the static member
Counter a;
Counter::getCount(); // => 1
Counter b, c;
Counter::getCount(); // => 3

Inheritance

// base class
class Animal {
// access specifier: accessible within the class and derived class
protected:
    int age;
public:
    void type() { std::cout << "I'm an animal!" << std::endl; }
    void eat() { std::cout << "yum yum~" << std::endl; }
};

// derived class
class Cat : public Animal {
public:
    // override the base class method
    void type() { std::cout << "I'm a cat!" << std::endl; }
    // implement a new method
    void meow() {
        std::cout << "mew mew~ I'm "
                  << age  // inherited from Animal
                  << " years old!"
                  << std::endl;
    }
};

Cat pet;
pet.eat();  // inherited from Animal
pet.meow(); // defined in Cat

Sometimes multiple classes share a common base class, and they need to access the common base class members. In this case, virtual inheritance is used, to avoid ambiguity (the common base class is inherited only once).

class Animal { /* ... */ };
class Dog : virtual public Animal { /* ... */ };
class Cat : virtual public Animal { /* ... */ };
class SiameseCat : public Dog, public Cat { /* ... */ };

When defining a base class, we can use virtual keyword to make a method overridable by derived classes; override keyword to ensure that the method is indeed overriding a base class method; and final keyword to mark a method as not overridable.

It's also possible to define a pure virtual method, which has no implementation and must be overridden by derived classes.

class Animal {
public:
    // virtual method (with default implementation)
    virtual void info() { std::cout << "I'm an animal!" << std::endl; }
    // pure virtual method (no implementation, must be overridden)
    virtual void eat() = 0;
};

class Feline : public Animal {
public:
    // override the base class method
    void info() override { std::cout << "I'm a feline!" << std::endl; }
    // override the pure virtual method
    void eat() override { std::cout << "I eat meat!" << std::endl; }
};

class Cat : public Feline {
public:
    // make final overrides
    void info() override final { std::cout << "I'm a cat!" << std::endl; }
    void eat() override final { std::cout << "I eat fish!" << std::endl; }
};

Polymorphism

Polymorphism is the ability to treat objects of different derived classes through a common interface (base class).

// dynamic dispatch (runtime polymorphism)
Cat saplyn;
Animal* ani_ptr = &saplyn;
Animal& ani_ref = saplyn;

ani_ptr->type(); // calls Cat::type()
ani_ref.type();  // calls Cat::type()

// however, the following will result in slicing
Animal ani = saplyn; // only the base part of saplyn is copied
ani.type();      // calls Animal::type()

Operator Overloading

The operators that can be overloaded are listed below:

  • Binary Operators: +, -, *, /, %, ^, &, |, =, <<, >>, , , ->*, ->, []
  • Comparison Operators: <, >, <=, >=, ==, !=
  • Short-Circuit Operators: &&, ||
  • Unary Operators: ~, !
  • Pre/Post Unary Operators: ++, --
  • Function Call Operator: ()

Note that . (dot operator) and ? : (ternary operator) cannot be overloaded. Additionally, the behavior of some operators (like the short-circuit behavior of logical operators && and ||) cannot be changed.

class Point {
private:
    double x;
    double y;
public:
    Point() = default;
    Point(double x = 0, double y = 0) : x(x), y(y) {}

    double get_x() const { return x; }
    double get_y() const { return y; }
};

// overload the `+` operator
Point operator+(const Point& a, const Point& b) {
    return Point{a.get_x() + b.get_x(), a.get_y() + b.get_y()};
}

// overload the `<<` operator
std::ostream& operator<<(std::ostream& os, const Point& p) {
    return os << '(' << p.get_x() << ", " << p.get_y() << ')';
}

In C++, some operators have multiple forms (pre/post-incr, etc.), they are overloaded with different signatures.

// pre-increment (++a)
Type& operator++();
// post-increment (a++)
Type operator++(int);

// l-value []
Type& operator[](int i);
// r-value []
Type operator[](int i) const;

Template

Generics

In C++, templates are a way to make functions and classes generic, so that they can handle any data type. This happens at compile time (static dispatch).

// `T` is a type parameter, it can be any type
template <typename T>
void swap(T& a, T& b) {
    T temp = a;
    a = b;
    b = temp;
}
int a = 1, b = 2;
swap<int>(a, b);
double c = 1.1, d = 2.2;
swap<double>(c, d);
// multiple type parameters also possible
template <typename T, typename U>
class Pair { /* ... */ };
Pair<int, double> p;
Pair<int, int> q;

It's possible to create specialized versions of a template for specific types.

// generic version
template <typename T>
void print(const T& value) { /* ... */ }

// specialized version for `std::string`
template <>
void print<std::string>(const std::string& value) { /* ... */ }

Variadic Templates

Variadic templates allow a function or class to accept any number of arguments of any type.

// base case
void print() { std::cout << std::endl; }
// recursive case
template <typename T, typename... Args>
void print(const T& first_arg, const Args&... args) {
    std::cout << first_arg << ' ';
    print(args...);
}
print(1, 2.2, "three");
// `sizeof...(args)` gives the number of arguments
template <typename... Args>
void count_args(const Args&... args) {
    std::cout << sizeof...(args) << std::endl;
}
count_args(1, 2.2, "three"); // => 3

Meta-programming

constexpr is a keyword used to declare a constant expression. It indicates that a variable or function can be evaluated at compile-time and its value can be used in other compile-time expressions.

// constexpr variables
constexpr int length = 10;
constexpr int width = 4 + 6;

// constexpr functions
constexpr int factorial(int n) {
    return n <= 1 ? 1 : (n * factorial(n - 1));
}

We can use meta-programming techniques to perform calculations and generate compile-time constants or data structures to optimize code or providing compile-time configuration options.

// compile-time Fibonacci sequence
template <int N>
struct Fibonacci {
    static const int value = Fibonacci<N - 1>::value + Fibonacci<N - 2>::value;
};
template <> struct Fibonacci<0> { static const int value = 0; };
template <> struct Fibonacci<1> { static const int value = 1; };

// usage
int fib = Fibonacci<10>::value; // => 55

Exception

Exception Handling

When executing a program, error occurs. It's important to handle the error properly. In C++, exception is the idiomatic way to handle error.

// `try` tries to execute the block of code
// and hands over to `catch` if an exception is thrown
try {
    int numerator = 10;
    int denominator = 0;
    int result = numerator / denominator;  // Potential division by zero

    // This line won't be executed if an exception is thrown above
    std::cout << "Result: " << result << std::endl;
} catch (const std::exception& e) {
    // handle the exception
    std::cout << "An exception occurred: " << e.what() << std::endl;
}
// `catch` can be chained to handle specific exceptions
try {
    std::string s = "hello";
    s[10] = 'x';  // Accessing an invalid index throws an out_of_range exception
} catch (const std::out_of_range& e) {
    std::cout << "Out of range exception: " << e.what() << std::endl;
} catch (const std::exception& e) {
    std::cout << "An exception occurred: " << e.what() << std::endl;
} catch (...) {
    std::cout << "An unknown exception occurred." << std::endl;
}
// `throw` can be used to throw an exception manually
void func() {
    // when exception is thrown, the control is transferred to the nearest `catch` block
    throw std::runtime_error("An error occurred in func()");
    // unreachable code
    std::cout << "This line won't be executed" << std::endl;
}
try {
    func();
} catch (const std::exception& e) {
    std::cout << "An exception occurred: " << e.what() << std::endl;
}

noexcept Guarantee

The noexcept keyword is used to declare that a function does not throw any exceptions. If a function marked with noexcept does throw an exception, the std::terminate function is called, terminating the program.

// won't fail guarantee
void func() noexcept { /* ... */ }

// specialized won't fail
template <> void func<int>() noexcept { /* ... */ }
template <> void func() noexcept(sizeof(T) == 4) { /* ... */ }

Custom Exception

We can define custom exception by inheriting from std::exception.

#include <stdexcept>

class MyException : public exception {
private:
    std::string m_message;
public:
    MyException(const std::string& msg) : m_message(msg) {}

    std::string what() override { return m_message; }
};

try {
    throw MyException("An error occurred in func()");
} catch (const std::exception& e) {
    std::cout << "An exception occurred: " << e.what() << std::endl;
}
// specialized exception
#include <stdexcept>

class RunOutOfMemory : public std::runtime_error {
public:
    RunOutOfMemory(const std::string& msg) : std::runtime_error(msg) {}
};

Rust

// Guess the number game
// https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html
use rand::Rng; // external crate
use std::cmp::Ordering;
use std::io;

fn generate_secret_number(min: u32, max: u32) -> u32 {
    rand::thread_rng().gen_range(min..=max)
}

fn main() {
    println!("Guess the number!");

    let secret_number = generate_secret_number(1, 100);

    loop {
        println!("Please input your guess.");

        let mut guess = String::new();

        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("You guessed: {guess}");

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
}

Types

  • Primitives:
    • u8, u16, u32, u64, u128, usize
    • i8, i16, i32, i64, i128, isize
    • f32, f64; char; bool
  • Constants: const, static
  • Mutability: let mut x = 5;
  • Reference: &x, &mut x
  • Casting: 5 as u8
  • Arrays: [i32; 5]
  • Tuples: (i32, f64, char)

Operators

  • Arithmetic: +, -, *, /, %
  • Relational: ==, !=, <, <=, >, >=
  • Logical: &&, ||, !
  • Bitwise: &, |, ^, <<, >>
  • Assignment: =, +=, -=, *=, /=, %=

Branching

// if-else expression
if condition { /* ... */ }
else if condition { /* ... */ }
else { /* ... */ }

// match expression
match value {
    pattern1 => /* ... */,
    pattern2 => /* ... */,
    _ => /* ... */,  // catch-all (wildcard)
}

// if let expression
if let Some(x) = compute() {
    println!("got a number: {}", x);
} else {
    println!("got nothing");
}

// let else statement
let x = Some(5) else {
    println!("got nothing");
    return;
};

Looping

// loop expression (while true)
loop { /* ... */ }

// while loop
while condition { /* ... */ }

// for loop
for elem in iter { /* ... */ }

// range
1..=5 // 1, 2, 3, 4, 5
1..5  // 1, 2, 3, 4

// break and continue
loop {
    if condition { break; }
    if condition { continue; }
}

// `break` with label.
'outer: loop {
    'inner: loop {
        break 'outer;
    }
}

// `break` with value
let result = loop {
    if condition { break 1; }
    else { break 2; }
};

// while let loop
while let Some(top) = stack.pop() {
    println!("{}", top);
}

Pattern Matching

// Destructuring a tuple
let (x, y, z) = (1, 2, 3);
let (first, .., last) = (2, 4, 8, 16, 32);
for (index, value) in v.iter().enumerate() {
    println!("{} is at index {}", value, index);
}

// Destructuring a struct
let Point { x, y } = Point::new(1, 2);   // extract x and y
let Vec3 { z, .. } = Vec3::new(4, 5, 6); // extract z, ignore others

// Destructuring an enum
let Some(x) = compute();

// matching a enum
match compute() {
    Some(x) => println!("got a number: {}", x),
    None => println!("got nothing"),
}

// various match patterns
match x {
    1 => println!("one"),
    2 => println!("two"),
    3 | 4 => println!("three or four"),
    5..=10 => println!("five through ten"),
    _ => {
        print!("it ");
        print!("can ");
        print!("be ");
        println!("anything!");
    },
}

// match guards
match x {
    Some(0) => println!("zero"),
    Some(n) if n < 0 => println!("negative"),
    Some(n) if n > 0 => println!("positive"),
    _ => println!("nothing"),
}

// @ bindings
match x {
    Some(n @ 0) => println!("zero: {}", n),
    Some(n) => println!("non-zero: {}", n),
    None => println!("nothing"),
}

// @ bindings with renaming
let msg = Message::Hello { id: 5 };
match msg {
    Message::Hello {
        id: id_variable @ 3..=7,
    } => println!("Found an id in range: {}", id_variable),
    Message::Hello { id: 10..=12 } => {
        println!("Found an id in another range")
    }
    Message::Hello { id } => println!("Found some other id: {}", id),
}

Struct

struct Point {
    // fields are private by default
    x: i32,
    // `pub` makes the field public
    pub y: i32,
}

// methods
impl Point {
    // associated function, also constructor
    fn new(x: i32, y: i32) -> Point {
        Point { x, y }
    }
    // captures `self` by immutable reference
    fn get_x(&self) -> i32 {
        self.x
    }
    // captures `self` by mutable reference
    fn set_x(&mut self, x: i32) {
        self.x = x;
    }
}

// method implementation can be separated
impl Point {
    // consumes `self`
    fn move_to(self, x: i32, y: i32) -> Point {
        Point { x, y }
    }
}

// construction
let p0 = Point::new(0, 0);
let x = p.get_x();
let p1 = Point {
    x, // field init shorthand
    y: 4
};
let p2 = Point { y: 4, ..p1 }; // struct update syntax

// tuple struct
struct Color(i32, i32, i32);
let black = Color(0, 0, 0); // construction
let r = black.0; // accessing fields by index
let g = black.1;
let b = black.2;

// unit-like struct (no fields, marker)
struct PlayerMarker;

Enum

// marker enum
enum IpAddrKind {
    V4,
    V6,
}
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;

// with data
enum AppError {
    NoContext,
    IoError(io::Error),
    InvalidInput {
        encountered: String,
        expected: String
    },
}
let unknown = AppError::NoContext;
let io_err = AppError::IoError(/* ... */);
let invalid = AppError::InvalidInput {
    encountered: "sapling".to_string(),
    expected: "saplyn".to_string()
};

// methods
impl AppError {
    fn is_io_error(&self) -> bool {
        match self {
            AppError::IoError(_) => true,
            _ => false,
        }
    }
}

Fundamental

Macros

Macros in rust are "code that writes code". For simplicity, they can be thought of as:

  • a function call, like: println!()
  • an attribute to add metadata to some item, like: #[derive(Debug)]
  • a way to define new syntax, like: vec![]
// function-like macro
println!("Hello, world!");             // print to stdout
eprintln!("An error occurred: {}", e); // print to stderr
dbg!(Object);                          // debug print

// attribute-like macro
#[derive(Debug)] // derive `Debug` trait for the struct
struct Object;

Common Types

Rust has a powerful ans expressive type system. Here are some commonly used non-primitive types.

String       // growable UTF-8 encoded text
Vec<T>       // growable array
Option<T>    // optional value. Rust's version of `null` or something
Result<T, E> // failable value
Box<T>       // heap-allocated value
&str         // string slice
&[T]         // array slice

Ownership & Borrowing

  • Ownership
    • Every value in Rust has a variable that's its owner.
    • When the owner goes out of scope, the value gets dropped.
    • When copying a value, the ownership is transferred. (if type not Copy)
  • Borrowing
    • A reference to a value, without taking ownership.
    • At any given time, there can be either one mutable reference or any number of immutable references.
    • References must always be valid.

Error Handling

In Rust, there're two ways to handle errors: panic!() and Result<T, E>. panic!() terminates the program, indicating a unrecoverable error; while Result<T, E> is used to handle recoverable errors. For when to panic!(), see The Book.

enum AppError {
    SaplynIsNotCat,
}

// failable function, returns `Result<T, E>`
fn failable() -> Result<String, AppError> {
    if saplyn.is_cat() {
        Ok(String::from("I'm a cat!"))
    } else {
        Err(AppError::SaplynIsNotCat)
    }
}

fn main() {
    let result = failable(); // call failable function
    match result {
        // is value? compute value
        Ok(val) => println!("Got value: {}", val),
        // is error? handle error
        Err(err) => {
            // unrecoverable error, panic!
            panic!("What?! I should be a cat!")
        },
    }
}

Sometimes, we are not handling the error right away, but passing it to the caller. In this case, we use ? to propagate the error.

// https://lib.rs/crates/anyhow

fn compute() -> anyhow::Result<i32> { Ok(114) }

fn action() -> anyhow::Result<i32> {
    let val = compute()?;  // propagate error or extracting value
    Ok(val * 1000 + 514)
}

fn long_chain() -> anyhow::Result<()> {
    let a = action()?;
    let b = action()?;
    let c = action()?;
    Ok(a + b + c)
}

Function & Closure

Function Item

Function items are zero-sized. Every none generic function has a unique, unnameable function item type.

fn callable() {}
let fn_item = callable; // function item: fn() {callable}

fn generic<T>() {}
let mut fn_i32 = generic::<i32>; // fn() {generic::<i32>}
// fn_i32 = generic::<u32>; // ERROR: mismatched types, fn() {generic::<u32>}

// zero-sized
println!("{}", std::mem::size_of(fn_item))
println!("{}", std::mem::size_of(fn_i32))

Function Pointer: fn

Function pointer are pointers that points to a function, referring to a function whose identity is not known at compile time.

// a function that takes two `i32` and returns one `i32`
type BinOp = fn(i32, i32) -> i32;

fn add(a: i32, b: i32) -> i32 { a + b }
fn minus(a: i32, b: i32) -> i32 { a - b }

// coerce: from "fn item" to "fn pointer"
let mut bo: BinOp = add;
bo = minus;

Closure

Closure is a function that is able to captures its environment.

// none-capturing closure
let add = |a: i32, b: i32| a + b;

// capturing closure (immutable)
let x = 10;
let read_x = |a: i32| a + x;

// capturing closure (mutable)
let mut x = 10;
let mut_x = |a: i32| { x += a; };

// capturing closure (move)
let x = vec![1, 2, 3];
let drop_x = move || { take(x); };

At compiler implementation level, closures are divided into two types:

  • None-capturing: They are like unnamed function, aka. instructions written in the code.
  • Capturing: They are like a struct that contains the captured variables, which also implements the corresponding function traits.
// consider a capturing closure like this
let x = 10;
let read_x = |a: i32| a + x;
read_x(5);

// it would be interpreted as something like this
let x = 10;
struct ReadX {
    x: i32,
}
impl Fn(i32) -> i32 for ReadX {
    fn call(&self, a: i32) -> i32 {
        a + self.x
    }
}
impl FnMut(i32) -> i32 for ReadX { /* ... */ }
impl FnOnce(i32) -> i32 for ReadX { /* ... */ }
let read_x = ReadX { x };
read_x.call(5);

Function Traits: Fn, FnMut & FnOnce

Fn, FnMut and FnOnce are traits that are implemented on function types.

  • Fn: The function can be called immutably, modifying nothing.
  • FnMut: The function can be called mutably, modifying captured data.
  • FnOnce: The function can be called only once, moving the captured value.
fn add(a: i32, b: i32) -> i32 { a + b }
fn call_it<F: Fn(i32, i32) -> i32>(f: F, a: i32, b: i32) -> i32 {
    f(a, b)
}

call_it(add, 1, 2);

Note that Fn is a super trait of FnMut, and FnMut is a super trait of FnOnce, because every "shared" function can be called "exclusively", and every "exclusive" function can be called "at least once".

fn add(a: i32, b: i32) -> i32 { a + b }
fn call_once<F: FnOnce(i32, i32) -> i32>(f: F, a: i32, b: i32) -> i32 {
    f(a, b)
}
fn call_mut<F: FnMut(i32, i32) -> i32>(f: F, a: i32, b: i32) -> i32 {
    f(a, b)
}

call_once(add, 1, 2);
call_mut(add, 1, 2);

Trait & Generic

Generic

Generic in Rust is implemented as type parameters. It allows us to define functions, structs, enums, and methods that work with arbitrary types. Generic happens at compile time.

// generic struct
struct Point<T> {
    x: T,
    y: T,
}

// generic method
impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}
// generic enum
enum Either<L, R> {
    Left(L),
    Right(R),
}

// generic function
fn swap<T>(pair: (T, T)) -> (T, T) {
    (pair.1, pair.0)
}

Trait

A trait defines functionality a particular type has and can share with other types.

// define a trait
trait Animal {
    // method blueprint
    fn eat(&mut self);
    // method with default implementation
    fn info(&self) -> String {
        String::from("I'm an animal")
    }
}

// implement a trait
struct Cat;
impl Animal for Cat {
    // implement method
    fn eat(&mut self) {
        println!("I'm eating fish");
    }
    // override default implementation
    fn info(&self) -> String {
        String::from("I'm a cat")
    }
}

When combined with generic, we can use trait bounds to specify that a type parameter has specific behaviors.

// generic function with trait bound
use std::fmt::Display;
fn print<T: Display>(val: T) {  // `T` implement `Display` trait (`T` is displayable)
    println!("{}", val);
}

use std::fmt::Debug;
use std::clone::Clone;
fn clone_and_print<T>(val: T)
where
    T: Debug + Clone // `T` implement `Debug` and `Clone` trait
{
    let cloned = val.clone();
    println!("{:?}", cloned);
}

They are similar two ways to achieve polymorphism in Rust: impl Trait which happens at compile time, and dyn Trait which happens at runtime.

// `impl Trait` (static dispatch)
fn print_info(animal: impl Animal) {
    println!("{}", animal.info());
}

// `&dyn Trait` (dynamic dispatch)
fn print_info_dyn(animal: &dyn Animal) {
    println!("{}", animal.info());
}

// `Box<dyn Trait>` (dynamic dispatch)
fn print_info_box(animal: Box<dyn Animal>) {
    println!("{}", animal.info());
}

Traits can also be generic, they may also have associated types.

// generic trait
trait Into<T> {
    // type associated with the trait
    type Output = T;
    // method blueprint
    fn Into(self) -> Self::Output;
}

// implement generic trait
struct Cat;
impl Into<String> for Cat {
    fn Into(self) -> String {
        String::from("I'm a cat!")
    }
}

Lifetime

Lifetimes are another kind of generic, but for references. Rather than ensuring that a type has the behavior we want, lifetimes ensure that references are valid as long as we need them to be.

Note that lifetime annotations do not define the lifetime of references, but bridge the relationship between them. The actual lifetime is determined by the value and reference flow.

// lifetime annotation
&str         // a reference
&'a str      // a reference with an explicit lifetime
&'a mut str  // a mutable reference with an explicit lifetime
&'static str // a reference with the 'static lifetime (the entire program)

// function with lifetime
// `x` and `y` have the same lifetime `'a`, so does the return value
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

Type State Pattern

Type state pattern is a design pattern that allows a type to change its behavior based on its associated state.

use std::marker::PhantomData; // A marker type that takes no space

// state type
struct Locked;
struct Unlocked;

// stateful type (state default to `Locked`)
struct Safe<State = Locked> {
    treasure: String,
    _state: PhantomData<State>, // state marker to satisfy the type system
}

// `Locked` specialized impl
impl Safe<Locked> {
    pub fn unlock(self, _pswd: String) -> Safe<Unlocked> {
        Safe {
            treasure: self.treasure,
            _state: PhantomData,
        }
    }
}

// `Locked` specialized impl (state elided)
impl Safe {
    pub fn new() -> Self {
        Self {
            treasure: "treasure".to_string(),
            _state: PhantomData,
        }
    }
}

// `Unlocked` specialized impl
impl Safe<Unlocked> {
    pub fn lock(self) -> Safe<Locked> {
        Safe {
            treasure: self.treasure,
            _state: PhantomData,
        }
    }
    pub fn treasure(&self) -> String {
        self.treasure.clone()
    }
}

// state independent impl (shared)
impl<State> Safe<State> {
    pub fn version(&self) {}
    pub fn encryption(&self) {}
}

fn main() {
    let safe = Safe::new();
    safe.version();
    safe.encryption();
    // no method named `treasure` found for struct `Safe` in the current scope
    // safe.treasure();

    let safe = safe.unlock("secret".to_string());
    safe.version();
    safe.encryption();
    safe.treasure();

    let safe = safe.lock();
}

Sync & Locks

Threading

New threads are spawned using std::thread::spawn() function, it takes the callable it would execute as an argument, and stops once the it returns.

fn main() {
    let remember = std::thread::spawn(f);
    let forget = std::thread::spawn(f);

    println!("Hello from the main thread.");

    remember.join().unwrap(); // Wait for thread `remember` to finish
    // `forget` would be terminated immediately after this line, regardless of
    // it finished or not.
}

fn f() {
    println!("Hello from another thread!");

    let id = std::thread::current().id();
    println!("This is my thread id: {:?}", id);
}

Scoped threads are threads that are spawned within a scope, It allows us to spawn threads that cannot outlive the scope of the closure we pass to that function, making it possible to safely borrow local variables.

let numbers = vec![1, 2, 3];

thread::scope(|s /* the scope */| {
    s.spawn(|| {
        println!("length: {}", numbers.len());
    });
    s.spawn(|| {
        for n in &numbers {
            println!("{n}");
        }
    });
});

Sharing State

There are several ways to share state that can be accessed by multiple threads:

  • Statics: Lives for the entire duration of the program.
static X: [i32; 3] = [1, 2, 3];

std::thread::spawn(|| dbg!(&X));
std::thread::spawn(|| dbg!(&X));
  • Leaks: Leaking a value, promising to never drop it.
let x: &'static [i32; 3] = Box::leak(Box::new([1, 2, 3]));

std::thread::spawn(move || dbg!(x));
std::thread::spawn(move || dbg!(x));
  • Reference Counting: Keep track of the number of owners, effectively sharing ownership of the value. The value is dropped when the last owner is dropped.
let a = std::rc::Rc::new([1, 2, 3]); // reference counted (non-thread-safe)
let b = a.clone();
assert_eq!(a.as_ptr(), b.as_ptr()); // Same allocation!

let c = std::sync::Arc::new([1, 2, 3]); // atomic rc (thread-safe, immutable)
let d = c.clone();
thread::spawn(move || dbg!(c));
thread::spawn(move || dbg!(d));

Interior Mutability

  • Cell<T>: A wrapper which allows replacement (mutation) through a shared reference for types that implement Copy.
use std::cell::Cell;

fn f(a: &Cell<i32>, b: &Cell<i32>) {
    let before = a.get();
    b.set(b.get() + 1);
    let after = a.get();
    if before != after {
        x(); // might happen
    }
}
  • RefCell<T>: A wrapper which allows borrowing (mutable or immutable) through a shared reference, enforcing the borrowing rules at runtime. It keeps track of any outstanding borrows and panics if the rules are violated.
use std::cell::RefCell;

fn f(v: &RefCell<Vec<i32>>) {
    v.borrow_mut().push(1); // We can modify the `Vec` directly.
}
  • Mutex<T> & RwLock<T>: Exclusive lock and read-write lock. It blocks the current threads on conflicting borrows, waiting for the lock to be released.

  • Atomics: Types that can be modified atomically, without locks. They require support from the processor to avoid data races.

Send & Sync

  • Send: A type is Send if it can be safely sent to another thread. In other words, if ownership of a value of that type can be transferred to another thread.
  • Sync: A type is Sync if it can be safely shared with another thread. In other words, a type T is Sync if and only if a shared reference to that type, &T, is Send.

All primitive types and most standard library types are Send and Sync, as they are auto traits that are implemented automatically by the compiler. One way to opt out of them is to use std::marker::PhantomData<T>, which is a zero-sized marker type.

use std::marker::PhantomData;

struct X {
    handle: i32,
    _not_sync: PhantomData<Cell<()>>,
}

Locking

  • Poisoning: If a thread panics while holding a lock, the lock is poisoned, and any subsequent attempts to acquire the lock will fail with an error. The error contains a guard that can be used to correct the inconsistency.
  • Guard: When acquiring a lock, a guard is returned, which is a smart pointer that automatically releases the lock when it goes out of scope. So it is recommended that the user keeps the guard in a variable, instead of using it in place as a temporary, to avoid releasing the lock unexpectedly.
let mut num_guard = num.lock().unwrap();
for _ in 0..100 {
    *num_guard += 1;
}
drop(num_guard); // Manually drop the guard, releasing the lock

Waiting

  • Thread parking: A thread can park itself, which puts it to sleep, stopping it from consuming any CPU cycles. Another thread can then wake it up by unparking.
use std::collections::VecDeque;

fn main() {
    let queue = Mutex::new(VecDeque::new());

    thread::scope(|s| {
        // Consuming thread
        let t = s.spawn(|| loop {
            let item = queue.lock().unwrap().pop_front();
            if let Some(item) = item {
                dbg!(item);
            } else {
                thread::park(); // Nothing to consume, park the thread
            }
        });

        // Producing thread
        for i in 0.. {
            queue.lock().unwrap().push_back(i);
            t.thread().unpark(); // New item, unpark, wake up the consuming thread
            thread::sleep(Duration::from_secs(1));
        }
    });
}
  • Condition variables: A condition variable is a synchronization primitive that has two main operations: wait() and notify(). One for waiting for a condition to be met, and the other for notifying one or more threads that are waiting for that condition. It is often used in conjunction with a Mutex<T>.
use std::sync::Condvar;

let queue = Mutex::new(VecDeque::new());
let not_empty = Condvar::new();

thread::scope(|s| {
    s.spawn(|| {
        loop {
            let mut q = queue.lock().unwrap();
            let item = loop {
                if let Some(item) = q.pop_front() {
                    break item;
                } else {
                    q = not_empty.wait(q).unwrap();
                }
            };
            drop(q);
            dbg!(item);
        }
    });

    for i in 0.. {
        queue.lock().unwrap().push_back(i);
        not_empty.notify_one();
        thread::sleep(Duration::from_secs(1));
    }
});
  • Barrier: A barrier is a synchronization primitive that allows multiple threads to wait for each other at a certain point in their execution. When all threads reach the barrier, they are all released to continue execution.
use std::sync::Barrier;
use std::thread;

let n = 10;
let barrier = Barrier::new(n);
thread::scope(|s| {
    for _ in 0..n {
        // The same messages will be printed together.
        // You will NOT see any interleaving.
        s.spawn(|| {
            println!("before wait");
            barrier.wait();
            println!("after wait");
        });
    }
});

Async

Warning

This page is imcomplete.

async, .await & impl Future

In Rust, asynchronous functions are defined by proceeding a synchronous function definition with async keyword which turns the return value into an impl Future<Output = SyncReturnType>, and are consumed by .await-ing it.

#![allow(unused)]
fn main() {
use trpl::Html;

//                             -> impl Future<Output = Option<String>>
async fn page_title(url: &str) -> Option<String> {
    let response = trpl::get(url).await;  // awaiting for an async function
    let response_text = response.text().await;
    Html::parse(&response_text)
        .select_first("title")
        .map(|title_element| title_element.inner_html())
}
}

Resources

Tools

Books

Presses

Zig

// Hello world program
const std = @import("std");

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();
    try stdout.print("Hello world!\n", .{});
}

Types

  • Primitives:
    • u8, u16, u32, u64, u128, usize
    • i8, i16, i32, i64, i128, isize

Operators

  • Arithmetic: +, -, *, /, %
  • Relational: ==, !=, <, <=, >, >=
  • Array manipulation: ++ (concatenation), ** (repeat)

Branching

// if-else statement
if (condition) {
    std.debug.print("Condition is true!\n", .{});
} else {
    std.debug.print("Condition is not true!\n", .{});
}

// if-else expression
const price: u8 = if (discount) 17 else 20;

// switch statement
switch (value) {
    1 => std.debug.print("Value is 1!\n", .{}),
    2 => std.debug.print("Value is 2!\n", .{}),
    // a switch must be exhaustive, `else` is used as "default" case
    else => std.debug.print("Value is something else!\n", .{}),
}

// switch expression
const output = switch (value) {
    1 => "Value is 1!",
    2 => "Value is 2!",
    else => "Value is something else!",
};

// unreachable
if (false) { unreachable; }

Looping

// while loop
while (n < 1024) {
    std.debug.print("{} ", .{n});
    n *= 2;
}

// while loop with continue expression
// (the continue expression invoked at the end of the loop
//  or when an explicit 'continue' is invoked)
while (n < 1024) : (n *= 2) {
    std.debug.print("{} ", .{n});
}

continue; // skips the current iteration
break; // exits the loop

// for loop
for (array) |item| {
    std.debug.print("{} ", .{item});
}
for (array, 0..) |item, index| {
    std.debug.print("{}: {} ", .{index, item});
}

Compound Types

Enum

// `Ops` is a `type`
const Ops = enum { inc, pow, dec };
const operations = [_]Ops{ // an array of Ops
    Ops.inc,
    Ops.pow,
    Ops.dec,
};
switch (op) {
    Ops.inc => { current_value += 1; },
    Ops.dec => { current_value -= 1; },
    Ops.pow => { current_value *= current_value; },
    // exhaustive switch, no need for `else`
}

const Color = enum(u32) { // using u32 as underlying type
    red = 0xff0000,
    green = 0x00ff00,
    blue = 0x0000ff,
};
std.debug.print( // multi-line string
    \\<p>
    \\  <span style="color: #{x:0>6}">Red</span>
    \\  <span style="color: #{x:0>6}">Green</span>
    \\  <span style="color: #{x:0>6}">Blue</span>
    \\</p>
    \\
, .{
    @intFromEnum(Color.red),
    @intFromEnum(Color.green),
    @intFromEnum(Color.blue), // Oops! We're missing something!
});

Struct

// `Point` is a `type`
const Point = struct {
    x: f64,
    y: f64,
};
var point = Point{ .x = 1.0, .y = 2.0 }; // create a Point instance
point.x = 3.0; // access and modify fields

// default value
const Character = struct {
    level: u8,
    health: u16 = 100, // default health is 100
};

Pointer

// declare `u8` ptr: *u8
// reference `num1`: &num1
var num1: u8 = 5;
const num1_pointer: *u8 = &num1;

var num2: u8 = undefined;
num2 = num1_pointer.*; // dereference `num1_pointer`: num1_pointer.*
num1_pointer.* = 10;   // modify the value pointed by `num1_pointer`

// pointer types
var mutable: u8 = 5; // `&mutable` is of type `*u8`
const value: u8 = 5; // `&value` is of type `*const u8`

// const pointer
const p1: *u8 = &locked; // cannot reassign, point to a specific value
var   p2: *u8 = &free;   // can be reassigned, point to other

Fundamental

Error Handling

In Zig, errors are represented as values, and is supported with native language features.

// define an error set
const NumberError = error{
    TooBig,
    TooSmall,
};

// failable function, returns an error union
// on error, it's `NumberError` variant
// on success, it's `i32` variant
fn failable() NumberError!i32 {
    if (number > 100) {
        return NumberError.TooBig; // specify error type
    } else if (number < 0) {
        return error.TooSmall;     // infer error type
    }
    return number;
}

// ignore error with default value
const fallback = failable() catch 50; // on error, fallback to 50

// handle error
const handle = failable() catch |err| {
    switch (err) {
        NumberError.TooBig => std.debug.print("Number is too big!\n", .{}),
        NumberError.TooSmall => std.debug.print("Number is too small!\n", .{}),
    }
};

// propagate error
fn anyhow() NumberError!void {
  const res = try failable();  // early return on error
}

// handle error with `if-else` & `switch`
const res = failable();
if (res) |val| { // <~ if success, `val` is the value
    std.debug.print("Value: {}\n", .{val});
} else |err| switch (err) { // <~ if error, `err` is the error. switch is not required
    NumberError.TooBig => std.debug.print("Number is too big!\n", .{}),
    NumberError.TooSmall => std.debug.print("Number is too small!\n", .{}),
}

Defer Execution

The defer keyword can be used to schedule a function to be executed when the current scope exits. The errdefer keyword can be used to schedule a function to be executed when the scope exits with an error.

// When a function may return in many places, `defer` can be used to ensure
// that a "tail" function is called at the end of the function, regardless of how
// the function exits.
fn printClosedWord(cond: bool) void {
    std.debug.print("(", .{});

    defer std.debug.print(") ", .{}); // <~ deferred

    if (cond) {
        std.debug.print("True", .{});
        return;
    } else {
        std.debug.print("False", .{});
        return;
    }
}

// When a function may early return with an error, `errdefer` can be used to
// ensure that a "error tail" function is called when the function exits with an
// error.
fn example() !void {
    errdefer std.debug.print("failed!\n", .{}); // <~ deferred (on error)
    const res = try failable(); // <~ possible early return
}

Bash

#!/bin/bash
# above is shebang, the path to the interpreter

echo "Hello, World!"

Variables & Arguments

# declare a variable
name="Saplyn"

# access the variable
echo "I'm $name!"

$? # exit status of the last command
$$ # process ID of the current script
$! # process ID of the last background command

$0        # name of the script
$1 ... $9 # arguments passed to the script
$#        # number of arguments passed to the script
$*        # all arguments passed to the script

${#name} # length of the variable
${#1}    # length of the first argument

Command & Substitution

# execute a command
echo "Hello, World!"

# command substitution
current_date=$(date)
echo "Today is $current_date. I'm $(whoami)"

Control Flow

Branching

# if-else
if true; then
    echo "It's true"
else
    echo "It's false"
fi

# case
case $1 in
    start)
        echo "Starting..."
        ;;
    stop)
        echo "Stopping..."
        ;;
    *)
        echo "Unknown command"
        ;;
esac

# number comparison
[$x -eq $y] # equal
[$x -ne $y] # not equal
[$x -gt $y] # greater than
[$x -lt $y] # less than
[$x -ge $y] # greater than or equal
[$x -le $y] # less than or equal

# string comparison
[$s =  $t] # equal
[$s != $t] # not equal
[$s <  $t] # less than
[$s >  $t] # greater than
[-z $s]    # empty
[-n $s]    # not empty

# file assertion
[-e $f] # exists
[-f $f] # is regular file
[-d $f] # is directory
[-r $f] # readable
[-w $f] # writable
[-x $f] # executable
[-s $f] # not empty
[-u $f] # SUID is set

# logical operators
[$cond -a $cond]     # and
[$cond -o $cond]     # or
! [$cond]            # not
[$cond1] && [$cond2] # short-circuit and
[$cond1] || [$cond2] # short-circuit or

Looping

# for-in
for i in 1 2 3; do
    echo $i
done
for i in {1..3}; do
    echo $i
done

# c-style for
for ((i=0; i<3; i++)); do
    echo $i
done

# while
i=0
while [ $i -le 5 ]; do
    echo "Executing \`while\` loop: $i"
    i=$((i + 1))
done

# until
i=0
until [ $i -ge 5 ]; do
    echo "Executing \`until\` loop: $i"
    i=$((i + 1))
done
# break
for i in {1..10}; do
    if [ "$i" -eq 5 ]; then
        break
    fi
    echo "Loop: $i"
done

# break multiple loops
for i in {1..10}; do
    for j in {1..2}; do
        if [ "$i" -eq 5 ]; then
            break 2
        fi
        echo "($i, $j)"
    done
done

# continue
for i in {1..10}; do
    if [ "$i" -eq 5 ]; then
        continue
    fi
    echo "Loop: $i"
done

# continue multiple loops
for i in {1..5}; do
    for j in {1..2}; do
        if [ "$i" -eq 3 ]; then
            continue 2
        fi
        echo -n "($i, $j) "
    done
    echo "($i)"
done

Exit

# exit
exit

# exit with status
exit 0 # success
exit 1 # failure (non-zero status)

Functions

# declaration
function greet() {
    echo "Hello, World!"
}
greet # call

# with arguments
function greet() {
    echo "Hello, $1!"
    echo "Hello, $2, too!"
}
greet "World" "Saplyn" # call

Go

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

Types

// declare a variable
var a int
var b int = 10 // with initialization
var c = 10     // type inferred
d := 10        // short hand

// declare multiple variables
var (
    e int
    f int = 10
    g = 10
    h := 10
)

// declare constants
const i int = 10
const j = 10
const (
    k int = 10
    l = 10
)

// array (fixed size)
var m [5]int
n := [5]int{1, 2, 3, 4, 5}
o := [...]int{1, 2, 3, 4, 5}  // size inferred

// slice (dynamic size)
p := []int{1, 2, 3, 4, 5}
q := n[1:3] // slice of n from index 1 to 3
r := n[:3]  // slice of n from index 0 to 3
s := make([]int, 5) // make a slice with length 5

// map (key-value)
t := map[string]int{
    "a": 1,
    "b": 2,
}
for k, v := range t {
    fmt.Println(k, v)
}

// types
int, int8, int16, int32, int64
uint, uint8, uint16, uint32, uint64, uintptr
float32, float64
complex64, complex128
byte // uint8
rune // (int32) a Unicode code point
string

// type conversion
i := 42
f := float64(i)
u := uint(f)

Operators

  • Arithmetic: +, -, *, /, %
  • Relational: ==, !=, <, <=, >, >=
  • Logical: &&, ||, !
  • Bitwise: &, |, ^, <<, >>
  • Assignment: =, +=, -=, *=, /=, %=

Flow Control

// if-else
if a > 10 {
    fmt.Println("a is greater than 10")
} else if a < 10 {
    fmt.Println("a is less than 10")
} else {
    fmt.Println("a is equal to 10")
}

// switch
switch a {
case 1:
    fmt.Println("a is 1")
case 2:
    fmt.Println("a is 2")
default:
    fmt.Println("a is neither 1 nor 2")
}
switch {  // switch true for long-chain if-else
case t.Hour() < 12:
    fmt.Println("Good morning!")
case t.Hour() < 17:
    fmt.Println("Good afternoon.")
default:
    fmt.Println("Good evening.")
}

// for (while-style)
x := 0
for x < 5 {
    fmt.Println(x)
    x++
}

// for (C-style)
for i := 0; i < 5; i++ {
    fmt.Println(i)
}

// for (range)
y := []int{1, 2, 3, 4, 5}
for i, v := range y {
    fmt.Println(i, v)
}
for _, v := range y {  // ignore index
    fmt.Println(v)
}

// infinite loop
for {
    fmt.Println("infinite loop")
}

break    // exit loop
continue // skip to next iteration

// defer
func main() {
    defer fmt.Println("world")
    fmt.Println("hello")
} // output: hello world

// defer stack
func main() {
    for i := 0; i < 5; i++ {
        defer fmt.Println(i)
    }
} // output: 4 3 2 1 0

Pointers

// declaration
var a int = 10
var b *int = &a // b is a pointer to a
var nothing *int // `nil` pointer

// dereference
fmt.Println(*b) // 10

// pass by reference (pointer's value)
func change(a *int) {
    *a = 20
}
change(&a)     // `&` address of
fmt.Println(a) // 20

Structs

// declaration
type person struct {
    name string
    age  int
}

// initialization
p := person{"Alice", 20}
q := person{
    name: "Bob",
    age:  30,
}
fmt.Println(p.name) // access fields

// receiver function (method)
func (p person) greet() {
    fmt.Println("Hello, my name is", p.name)
}
p.greet() // call method

// pointer receiver function
func (p *person) grow() {
    p.age++  // auto dereferenced
}
p.grow()

Interfaces

// declaration
type drawable interface {
    info() string
    draw()
}

// implementation
type circle struct {
    radius int
}
func (c circle) info() string {
    return "Circle"
}
func (c circle) draw() {
    fmt.Println("Drawing circle")
}

// polymorphism
func drawShape(d drawable) {
    fmt.Println(d.info())
    d.draw()
}
drawShape(circle{10})

// empty interface
func describe(i interface{}) {  // any type
    fmt.Printf("(%v, %T)\n", i, i)
}
describe(42)
describe("hello")

Essential

Error Handling

Go programs express error state with error values, a built-in interface.

// https://go.dev/tour/methods/19
// the error type
type error interface {
    Error() string
}

// failable & handling error
import "fmt"
i, err := strconv.Atoi("42")
if err != nil {
    fmt.Printf("couldn't convert number: %v\n", err)
    return
}
fmt.Println("Converted integer:", i)

// custom error
import "time"
type MyError struct {
    When time.Time
    What string
}
func (e *MyError) Error() string {
    return fmt.Sprintf("at %v, %s", e.When, e.What)
}
func run() error {
    return &MyError{ time.Now(), "it didn't work", }
}

Generics

Go functions can be written to work on multiple types using type parameters. The type parameters of a function appear between brackets, before the function's arguments.

// https://go.dev/tour/generics/1
// generic function
func Index[T comparable](s []T, x T) int {
    for i, v := range s {
        // v and x are type T, which is `comparable`
        if v == x {
            return i
        }
    }
    return -1
}

// generic type
type Stack[T any] struct {
    data []T
}

Goroutines

A goroutine is a lightweight thread managed by the Go runtime.

// https://go.dev/tour/concurrency/1
func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}
// `go` starts a new goroutine
func main() {
    go say("world")
    say("hello")
}

Channels

Channels are a typed conduit through which we can send and receive values with the channel operator <-. By default, sends and receives block until the other side is ready.

// https://go.dev/tour/concurrency/2
func sum(s []int, c chan int) {
    sum := 0
    for _, v := range s {
        sum += v
    }
    c <- sum // send sum to c
}

func main() {
    s := []int{7, 2, 8, -9, 4, 0}

    // make a channel of int
    c := make(chan int)
    // start two goroutines
    go sum(s[:len(s)/2], c)
    go sum(s[len(s)/2:], c)
    x, y := <-c, <-c // receive from c

    fmt.Println(x, y, x+y)
}

A sender can close a channel to indicate that no more values will be sent. Receivers can test whether a channel has been closed by assigning a second parameter to the receive expression.

// https://go.dev/tour/concurrency/4
func fibonacci(n int, c chan int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x+y
    }
    close(c)  // close the channel
    // can be tested by `v, ok := <-c`
}

func main() {
    // make a buffered channel
    c := make(chan int, 10)
    go fibonacci(cap(c), c)
    // receive from the channel until it's closed
    for i := range c {
        fmt.Println(i)
    }
}

Select

The select statement lets a goroutine wait on multiple communication operations. A select blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready.

// https://go.dev/tour/concurrency/6
func main() {
    tick := time.Tick(100 * time.Millisecond)
    boom := time.After(500 * time.Millisecond)
    for {
        select {
        case <-tick:
            fmt.Println("tick.")
        case <-boom:
            fmt.Println("BOOM!")
            return
        default:
            fmt.Println("    .")
            time.Sleep(50 * time.Millisecond)
        }
    }
}

Java

// hello world in Java
// `HelloWorld.java`
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello world!");
    }
}

Types

  • Primitives: int, char, float, double
  • Constants: final
  • Array: int[] arr = new int[5];

Operators

  • Arithmetic: +, -, *, /, %
  • Relational: ==, !=, <, <=, >, >=
  • Logical: &&, ||, !
  • Bitwise: &, |, ^, <<, >>, >>> (unsigned right shift)
  • Assignment: =, +=, -=, *=, /=, %=

Control Flow

// if statement
if (condition) {
    // ...
} else if (condition) {
    // ...
} else {
    // ...
}

// switch statement
switch (expression) {
    case value1:
        // ...
        break;
    case value2:
        // ...
        break;
    default:
        // ...
}

// while loop
int i = 0;
while (i < 10) {
    System.out.println(i);
}

// do-while loop
int i = 0;
do {
    System.out.println(i);
} while (i < 10);

// for loop
for (int i = 0; i < 10; i++) {
    System.out.println(i);
}

// for each loop
int[] numbers = {1, 2, 3, 4, 5};
for (int number : numbers) {
    System.out.println(number);
}

break;    // exits the loop
continue; // skips the current iteration.

// label jump
outerLoop:
for (int i = 0; i < 5; i++) {
    innerLoop:
    for (int j = 0; j < 5; j++) {
        if (i == 1 && j == 1) {
            // continue with the next iteration of the inner loop
            continue innerLoop;
        }
        if (i == 2 && j == 2) {
            // break out of the outer loop
            break outerLoop;
        }
        System.out.println(i);
    }
}

Object Oriented

Class as Object

In Java, every item has an access specifier:

  • public: accessible from anywhere
  • protected: accessible within the package and by subclass
  • (default): accessible within the package
  • private: accessible within the class
public class Point {
    // data members
    private double x;
    private double y;

    // method
    public void set(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public double dist(Point that) {
        return Math.sqrt(
            (x - that.x) * (x - that.x) +
            (y - that.y) * (y - that.y)
        );
    }
}

Point p = new Point();
p.set(3.0, 4.0);

Classes may have static members, which are shared by all objects (instances) of the same class (type).

public class Student {
    // fields
    String name;
    int age;

    // constructor
    Student(String name, int age) {
        /* ... */
    }

    // method
    void displayInfo() {
        System.out.println("Name: " + name);
        System.out.println("Age: " + age);
    }
}

Copy & Reference

In Java, everything that is not a primitive type is a reference type. When an object is assigned to another object, the reference is copied, not the object itself.

// shallow copy
// `obj1` and `obj2` now share the same memory address (reference)
Object obj1 = new Object();
Object obj2 = obj1;

// deep copy constructor
public class MyClass {
    private int number;
    private String text;

    // deep copy constructor
    public MyClass(MyClass original) {
        this.number = original.getNumber();
        this.text = original.getText();
    }
}

Finalize Method

Java is garbage-collected, so there is no need to explicitly release resources. We can, however, @Override the finalize method to release resources before the object is garbage-collected.

public class Student {
    /* ... */

    @Override
    protected void finalize() throws Throwable {
        // Perform cleanup actions before object is garbage collected
        super.finalize();  // call the finalize method of the superclass
        System.out.println("Finalizing Student object: " + name);
    }
}

Inheritance

Every class may extend another class, inheriting its members and making it its superclass. A subclass may @Override a method of its superclass. A subclass may access the members of its superclass via super keyword.

// Animal.java
public class Animal {
    private String name;

    public Animal(String name) {
        this.name = name;
    }

    public void makeSound() {
        System.out.println("The animal makes a sound.");
    }
}

// Dog.java
public class Dog extends Animal { // extends superclass
    public Dog(String name) {
        super(name);  // call superclass constructor
    }

    // override superclass method
    @Override
    public void makeSound() {
        super.makeSound();  // access superclass member
        System.out.println("Woof woof!");
        System.out.println("My name is " + super.name + "!");
    }
}

They can be abstract classes with abstract methods, which cannot be instantiated. They serve as blueprints for other classes.

// Animal.java
// abstract class, cannot be instantiated
public abstract class Animal {
    // abstract method, must be overridden by subclass
    public abstract void makeSound();

    // concrete method as default implementation
    public void sleep() {
        System.out.println("The animal is sleeping.");
    }
}

the final keyword prevents a class or method from being overridden.

// final class, cannot be extended
public final class NotExtendable {
    /* ... */
}

public class SomeClass {
    // final method, cannot be overridden
    public final void norOverridable() {
        System.out.println("Not overridable.");
    }
}

Interface

Interface is a contract defined for a class. It defines a set of abstract methods that a class can implement. A class may implement multiple interfaces.

// interface
interface Stringify {
    // method blueprint
    String toString();
}
interface Comparable {
    // default implementation
    default int compareTo(Object obj) {
        return 0;
    }
}

// class implementing the interface
class MyClass implements Stringify, Comparable {
    @Override
    public String toString() {
        return "MyClass";
    }

    @Override
    public int compareTo(Object obj) {
        /* ... */
    }
}

Stringify obj = new MyClass();
System.out.println(obj.toString());

Interfaces may also extend other interfaces.

interface Flyable {
    void fly();
}

interface Swimmable {
    void swim();
}

interface Walkable {
    void walk();
}

interface Amphibious extends Flyable, Swimmable, Walkable {
    void land();
}

Polymorphism

public class Animal {
    public void makeSound() { System.out.println("Animal is making a sound"); }
}
public class Dog extends Animal {
    @Override
    public void makeSound() { System.out.println("Dog is barking"); }
}
public class Cat extends Animal {
    @Override
    public void makeSound() { System.out.println("Cat is meowing"); }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal();
        Dog dog = new Dog();
        Cat cat = new Cat();

        System.out.println(animal instanceof Animal); // true
        System.out.println(dog instanceof Animal); // true
        System.out.println(cat instanceof Animal); // true
        System.out.println(dog instanceof Dog); // true
        System.out.println(cat instanceof Dog); // false


        Animal animal1 = new Animal();
        Animal animal2 = new Dog();
        Animal animal3 = new Cat();

        animal1.makeSound();  // Output: Animal is making a sound
        animal2.makeSound();  // Output: Dog is barking
        animal3.makeSound();  // Output: Cat is meowing
    }
}

Exception

Try, Catch & Finally

BufferedReader reader = null;

try {  // Code that might throw an exception
    reader = new BufferedReader(new FileReader("example.txt"));
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
} catch (FileNotFoundException e) {  // Handle the exception
    System.out.println("File not found: " + e.getMessage());
} catch (IOException e) {  // Handle the exception
    System.out.println("Error reading file: " + e.getMessage());
} finally {  // This code will always run
    try {
        if (reader != null) {
            reader.close();
        }
    } catch (IOException e) {
        System.out.println("Error closing file: " + e.getMessage());
    }
}

Throw & Throws Marker

The throw statement is used to explicitly throw an exception. And the throws tells the compiler that a method may throw an exception.

public void checkNumber(int num) throws NegativeNumberException {
    if (num < 0) {
        throw new NegativeNumberException("Number is negative: " + num);
    }
}

Their are two types of exceptions in Java: checked and unchecked. The checked exceptions are checked at compile-time, while the unchecked exceptions are not.

try {
    FileInputStream file = new FileInputStream("myFile.txt");
} catch (FileNotFoundException e) {
    // Handle the checked exception (e.g., provide an alternative file)
}

// ArithmeticException, an unchecked exception
int result = 10 / 0;

It's possible to define a custom exception by extending the Exception class.

class CustomCheckedException extends Exception {
    public CustomCheckedException(String message) {
        super(message);
    }
}

class MyUncheckedException extends RuntimeException {
    // Constructor and additional methods
}

Generic

Type Parameter

To work with generic object in Java, we define classes or interfaces with <T>.

public class Box<T> {
    private T value;

    public Box(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
}

Box<int> intObj = new Box<>(42);
Box<String> strObj = new Box<>("Hello, Generics!");

int intValue = intObj.getData();
String strValue = strObj.getData();

We can also use generic with methods.

public <T> T findMax(T[] arr) {
    if (arr == null || arr.length == 0) {
        return null;
    }

    T max = arr[0];
    for (T item : arr) {
        if (item.compareTo(max) > 0) {
            max = item;
        }
    }
    return max;
}

Vue

<!-- Single File Component -->
<template>
  <button @click="count++">Count is: {{ count }}</button>
</template>

<script setup lang="ts">
// Composition API
import { ref } from "vue";
const count = ref(0);
</script>

<style scoped>
button {
  font-weight: bold;
}
</style>

Single File Components (SFC)

A Vue component is typically defined in a single file with the .vue extension, which contains three sections:

  • <template>: HTML-like syntax for the component's structure.
  • <script>: JavaScript (or TypeScript) code for the component's logic.
  • <style>: CSS styles for the component, which can be scoped to avoid conflicts.

Resources

Ecosystem

  • Pinia: Store library for Vue
  • Vue Router: Official router for Vue
  • VueUse: Collection of Vue composition utilities
  • Nuxt: A progressive Vue framework
  • PrimeVue: Complete UI suite for Vue
  • Vite: A JS Build tool
  • Vitest: A Vite-native testing framework

Misc

  • VuePress: A Vue-powered static site generator

Nuxt.JS

Info

The Nuxt.JS has extensive documentation available at their official website, which covers:

So this note package won't be covering the details of Nuxt.JS.

Relm4

use adw::prelude::*;
use relm4::prelude::*;

struct AppModel {
    counter: u8,
}

#[derive(Debug)]
enum AppMsg {
    Increment,
    Decrement,
}

#[relm4::component]
impl SimpleComponent for AppModel {
    /// The type of data with which this component will be initialized.
    type Init = u8;
    /// The type of the messages that this component can receive.
    type Input = AppMsg;
    /// The type of the messages that this component can send.
    type Output = ();

    view! {
        gtk::Window {
            set_title: Some("Simple app"),
            set_default_width: 300,
            set_default_height: 100,

            gtk::Box {
                set_orientation: gtk::Orientation::Vertical,
                set_spacing: 5,
                set_margin_all: 5,

                gtk::Button {
                    set_label: "Increment",
                    connect_clicked => AppMsg::Increment
                },

                gtk::Button::with_label("Decrement") {
                    connect_clicked => AppMsg::Decrement
                },

                gtk::Label {
                    #[watch]
                    set_label: &format!("Counter: {}", model.counter),
                    set_margin_all: 5,
                }
            }
        }
    }

    // Initialize the UI.
    fn init(
        counter: Self::Init,
        root: Self::Root,
        sender: ComponentSender<Self>,
    ) -> ComponentParts<Self> {
        let model = AppModel { counter };

        // Insert the macro code generation here
        let widgets = view_output!();

        ComponentParts { model, widgets }
    }

    fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
        match msg {
            AppMsg::Increment => {
                self.counter = self.counter.wrapping_add(1);
            }
            AppMsg::Decrement => {
                self.counter = self.counter.wrapping_sub(1);
            }
        }
    }
}

fn main() {
    let app = RelmApp::new("app.relm4.counter_demo");
    app.run::<AppModel>(0);
}

The #[component] Macro

Note

This page is written purely based on observation of the behavior of the #[component] macro, and the documentation of it from the Relm4 book thus may not be complete or accurate.

The #[component] macro provides a convenient way to define UI using builder pattern.

#[relm4::component]
impl SimpleComponent for AppModel {
    type Init = AppInit; // The type of data with which this component will be initialized.
    type Input = AppMsg; // The type of the messages that this component can receive.
    type Output = ();    // The type of the messages that this component can send.

    view! {
        // The UI is defined here with `view!` macro syntax.
    }

    // Initialize the UI.
    fn init(
        init: Self::Init,
        root: Self::Root,
        sender: ComponentSender<Self>,
    ) -> ComponentParts<Self> {
        let model = AppModel { state: init };

        // Code generated by `view!{}` is inserted below.
        let widgets = view_output!();

        ComponentParts { model, widgets }
    }

    fn update(&mut self, msg: Self::Input, _sender: ComponentSender<Self>) {
        match msg {
            // Handle messages and update state.
        }
    }
}

Modifiers

  • #[component(pub)] makes the component public, allowing it to be used outside of its module.

view!{}

view! {
    gtk::Window {
        set_title: Some("Simple app"),
        set_default_width: 300,
        set_default_height: 100,

        gtk::Box {
            set_orientation: gtk::Orientation::Vertical,
            set_spacing: 5,
            set_margin_all: 5,
        }
    }
}

would get translated into something like this:

use relm4::RelmContainerExt;

let gtk_window = gtk::Window::default();
gtk_window.set_title(Some("Simple app"));
gtk_window.set_default_width(300);
gtk_window.set_default_height(100);

let gtk_box = gtk::Box::default();
gtk_box.set_orientation(gtk::Orientation::Vertical);
gtk_box.set_spacing(5);
gtk_box.set_margin_all(5);

gtk_window.container_add(&gtk_box);

:, = or =>

  • : would dutifully puts everything behind it into argument slot of the function call. If the function call takes multiple arguments, it should be followed by a tuple.
  • = would first tries to build what is behind it using builder pattern, and then puts the result into argument slot of the function call.
  • => have two variants:
    • when followed by a closure, it would be directly put into the argument slot of the function call.
    • when followed by anything else, it would first be turned into a closure like move |_| { sender.input(/* ... */) }, and then put into the argument slot.
view! {
    gtk::Button {
        set_label: "Click me",
        connect_clicked => AppMsg::Clicked
    }
}
view! { /* Widget */ {
    // `&` makes it take reference instead of ownership.
    set_child = &adw::ToolbarView {
        add_top_bar = &adw::HeaderBar {},
    }
}}
view! {
    gtk::Window {
        // set title if it is `Some(T)`, does nothing if otherwise.
        set_title?: optional_title,
    }
}

Attributes

  • #[watch] wathches the variables referenced in the widget, so that the UI will be updated when they change.
  • #[wrap(Some)] would wrap the widget in another type, typically an Option<T>, only effective when using =
  • #[name(widget_name)] would name the widget as widget_name, so that it can be referenced later in the UI construction.

Info

The following items exists, but their usage and purpose are yet to be studied and documented.

  • #[transition = "SlideRight"]
  • #[root]
  • #[local_ref]
  • #[iterate]
  • #[block_signal(handler_name)]

Conditional Widgets

#![allow(unused)]
fn main() {
view! {
    if model.value % 2 == 0 {
        gtk::Label {
            set_label: "The value is even",
        },
        gtk::Label {
            set_label: "The value is odd",
        }
    }
}
view! {
    match model.value {
        0..=9 => {
            gtk::Label {
                set_label: "The value is below 10",
            },
        }
        _ => {
            gtk::Label {
                set_label: "The value is equal or above 10",
            },
        }
    }
}
}

Advanced Syntaxes

Inline Function Initialization

#![allow(unused)]
fn main() {
view! { /* Widget */ {
    set_property_name = new_box() -> gtk::Box { ... }
}}
}

Additional Tailing Arguments

#![allow(unused)]
fn main() {
view! {
    gtk::Grid {
        attach[0, 0, 1, 2] = &gtk::Label { ... }
        // grid.attach(label, 0, 0, 1, 2)
    }
}
}

Naming Temporary Widgets

#![allow(unused)]
fn main() {
view! { /* Widget */ {
    set_child = &adw::ToolbarView {
        // Will name this widget `headerbar`.
        add_top_bar: headerbar = &adw::HeaderBar {},
        set_content: Some(&stack),
    }
}}
}

Accessing Returned Widgets

#![allow(unused)]
fn main() {
view! {
    gtk::Stack {
        add_child = &gtk::Label {
            set_label: "placeholder",
        } -> page: gtk::StackPage { // it can also be named (must specify type)
            // Access the returned widgets (in this case gtk::StackPage)
            set_title: "page title",
        }
    }
}
}

Clone for Signal Handlers

#![allow(unused)]
fn main() {
view! {
    // Clone with same name.
    connect_name[cloned_var1, cloned_var2] => move |arg1, arg2| {
        /* ... */
    },
    // Clone and rename.
    connect_name[sender = components.sender.clone()] => move |/* ... */| {
        /* ... */
    },
}
}

additional_fields!{}

This macro adds additional fields to the generated widget struct.

#[relm4::component]
// ...
    additional_fields! {
        stack: gtk::Stack,
    }

    fn init(
        init: Self::Init,
        root: Self::Root,
        sender: ComponentSender<Self>,
    ) -> ComponentParts<Self> {
        let counter = CounterModel::builder()
            .launch(init.0)
            .forward(sender.input_sender(), identity);
        let toggler = TogglerModel::builder()
            .launch(init.1)
            .forward(sender.input_sender(), identity);

        view! {
            stack = gtk::Stack {
                add_titled: (counter.widget(), None, "Counter"),
                add_titled: (toggler.widget(), None, "Toggle"),
                set_vhomogeneous: false,
            }
        } // produces a `gtk::Stack` widget named `stack`

        let model = AppModel { counter, toggler };
        let widgets = view_output!(); // picks up the `stack` widget

        let split_view = widgets.split_view.clone();
        widgets.stack.connect_visible_child_notify(move |_| {
            split_view.set_show_content(true);
        }); // can later be accessed

        ComponentParts { model, widgets }
    }
// ...

Info

This thing exists, but its usage and purpose are yet to be studied and documented.

pre_view() and post_view()

Info

This thing exists, but its usage and purpose are yet to be studied and documented.

Resources

GTK-RS Documentation

GTK Documentation

Warning

GTK documentation is licensed under LGPL-2.1-or-later, which is not compatible with more permissive licenses like MIT or Apache-2.0. These links are listed just for reference and non of the documentation is used in this note package.

Makepad

// app.rs
use makepad_widgets::*;

live_design! {
    use link::widgets::*;

    App = {{App}} {
        ui: <Root> {
            <Window> {
                body = <View> {}
            }
        }
    }
}

#[derive(Live, LiveHook)]
pub struct App {
    #[live]
    ui: WidgetRef,
}

impl LiveRegister for App {
    fn live_register(cx: &mut Cx) {
        makepad_widgets::live_design(cx);
    }
}

impl AppMain for App {
    fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
        self.ui.handle_event(cx, event, &mut Scope::empty());
    }
}

app_main!(App);

Live Design

The live_design!{} macro is used to define Makepad DSL code. DSL code is used in Makepad to define the layout and styling of our app, similar to CSS.

  • App = {{App}} { ... } defines an App. The {{App}} syntax links the definition of App to an App struct in the Rust code. Within it, we define the ui field as the root of our widget tree:
    • <Root> { ... } is our top-level container.
    • <Window> { ... } is a window on the screen.
    • body = <View> {} is an empty view named body.

The live design system enables Makepad's powerful runtime styling capabilities, allowing us to use Makepad Studio to tweak the app's design without recompiling.

Live, LiveHook, and LiveRegister

The Live trait enables a struct to interact with the live design system. When deriving Live, each field needs to be marked with one and only one of the following attributes:

  • #[live], which marks the field as part of the live design system. It would be initialized with the values defined in the live_design!{}, or Default::default() if failed to find one.
  • #[rust], which marks the field as an ordinary Rust field. It would be initialized with Default::default().

The LiveHook trait provides several overrideable methods that will be called at various points during our app's lifetime.

The LiveRegister trait is used to register DSL code. By registering DSL code, we make it available to the rest of our app. In our implementation, we call the makepad_widgets::live_design function to register the DSL code in the makepad_widgets crate. Without this call, we would not be able to use any of the built-in widgets such as Root, Window, and View that we saw earlier.

The AppMain Trait

The AppMain trait is used to hook an instance of the App struct into the main event loop.

A Scope in Makepad is a container that is used to pass both app-wide data and widget-specific props along with each event.

The Widget Trait

The Widget trait allows us to override the behaviour of a widget. However, deriving the Widget trait does not generate an implementation of it. Instead, it generates implementations of several helper traits that the Widget trait needs.

Resources

Documentation

Actions

Essential

Rust

Bun

.vimrc

syntax on

set number
set ruler
set cursorline
set mouse=a

set hlsearch
set incsearch
set showcmd

set clipboard=unnamedplus

set smartindent
set autoindent
set tabstop=4
set softtabstop=4
set shiftwidth=4
set expandtab

inoremap { {}<ESC>i

.bashrc

# Shell prompt customize
PROMPT_COMMAND=__prompt_command

__prompt_command() {
    local last_cmd_code="$?" # Need to be the first

    # Definition of colors
    local no_color='\[\e[0m\]'

    local st_bold='\[\e[1m\]'
    local st_underln='\[\e[4m\]'
    local st_reverse='\[\e[7m\]'

    local fg_black='\[\e[30m\]'
    local fg_red='\[\e[31m\]'
    local fg_green='\[\e[32m\]'
    local fg_yellow='\[\e[33m\]'
    local fg_blue='\[\e[34m\]'
    local fg_purple='\[\e[35m\]'
    local fg_cyan='\[\e[36m\]'
    local fg_white='\[\e[37m\]'

    local bg_black='\[\e[40m\]'
    local bg_red='\[\e[41m\]'
    local bg_green='\[\e[42m\]'
    local bg_yellow='\[\e[43m\]'
    local bg_blue='\[\e[44m\]'
    local bg_purple='\[\e[45m\]'
    local bg_cyan='\[\e[46m\]'
    local bg_white='\[\e[47m\]'

    # Initialize prompt
    PS1="\n${fg_blue}█${no_color}${bg_blue}${st_bold}$HOST${no_color}${fg_blue}█▓▒░${no_color}\n"
    PS2="${fg_blue}${st_bold}∙${no_color} "

    # Current working directory
    PS1+="${fg_cyan}${st_bold}\w${no_color}"
    PS1+="\n"

    # Last command status & prompt arrow
    if [ $last_cmd_code != 0 ]; then
        PS1+="${fg_red}${st_bold}❯${no_color}"
    else
        PS1+="${fg_green}${st_bold}❯${no_color}"
    fi
    PS1+=" "
}

# Quality of life aliases
alias l="ls -alF"
alias ll="ls -lF"

.tmux.conf

#!/usr/bin/env bash

# Enable color modes
set -g default-terminal "screen-256color"
set-option -ga terminal-overrides ",xterm-256color:Tc"

# Disable key escape
set -sg escape-time 0

# Quality of life
set -g mouse on
setw -g mode-keys vi
setw -g history-limit 10000000
set-option -g focus-events on

# Clipboard
set-option -s set-clipboard off
bind -T copy-mode-vi Enter send-keys -X copy-pipe-and-cancel "xclip -i -f -selection primary | xclip -i -selection clipboard"

# Quick resize
bind-key -r -T prefix C-h resize-pane -L 2
bind-key -r -T prefix C-j resize-pane -D 2
bind-key -r -T prefix C-k resize-pane -U 2
bind-key -r -T prefix C-l resize-pane -R 2

# Easy reload config file
bind r source-file ~/.tmux.conf

# Move around panes with hjkl
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R

# Splite Window
bind v split-window -h
bind s split-window -v

########## Styling ##########

# Pane border color
set-option -g pane-active-border-style fg=colour213
set-option -g pane-border-style fg=colour239

# Pane number display
set-option -g display-panes-active-colour colour201
set-option -g display-panes-colour colour189

# Clock
set-window-option -g clock-mode-colour colour207

# Enable status bar
set-option -g status "on"
set-option -g monitor-bell "on"
set-option -g monitor-activity "on"

# Status bar basic setting
set -g status-interval 1
set -g status-left-length 40
set -g status-right-length 30

# [win/cur{/}tab/----------/time]
set-window-option -g window-status-separator ""

# [win/cur/tab/{----------}/time]
set-option -g status-style bg=colour0,fg=colour255

# [{win}/cur/tab/----------/time]
set-option -g status-left "\
#[bg=#5e5faf, fg=colour189]#{?client_prefix,#[bg=colour167],}  #{?window_zoomed_flag, 󰻿 ,}#S \
#[bg=colour0, fg=#5e5faf]#{?client_prefix,#[fg=colour167],}"

# [win/{cur}/tab/----------/time]
set-window-option -g window-status-current-format "\
#[bg=colour0, fg=#383969] \
#[bg=#5e5faf, fg=colour189] #I \
#[bg=colour189, fg=#5e5faf] \
#[bg=colour189, fg=#5e5faf, bold]#W "

# [win/cur/{tab}/----------/time]
set-window-option -g window-status-format "\
#[bg=colour0, fg=#383969] \
#[bg=#383969, fg=colour189] #I \
#[bg=#383969, fg=colour189]#{?window_activity_flag,󱥂  ,}\
#[bg=colour189, fg=#383969]#{?window_bell_flag, 󱅫 ,}\
#[bg=#414349, fg=colour189] #W "

# [win/cur/{bell}/----------/time]
set-window-option -g window-status-bell-style bg=#414349,fg=colour189

# [win/cur/tab/----------/{time}]
set-option -g status-right "\
#[bg=#5e5faf, fg=colour189] %d %b %Y %H:%M "

# Window list
set-window-option -g mode-style bg=#5e5faf,fg=colour189

# Message, Activity
set-option -ag message-style bg=colour167,fg=colour189
set-option -ag window-status-activity-style none

Clang Kit

.clang-format

IndentWidth: 4
TabWidth: 4
UseTab: Never

.clang-tidy

Checks: >
  clang-analyzer-*,
  bugprone-*,
  readability-*

FormatStyle: file

CheckOptions:
  - { key: readability-identifier-naming.NamespaceCase,          value: lower_case }
  - { key: readability-identifier-naming.ClassCase,              value: CamelCase  }
  - { key: readability-identifier-naming.StructCase,             value: CamelCase  }
  - { key: readability-identifier-naming.TemplateParameterCase,  value: CamelCase  }
  - { key: readability-identifier-naming.FunctionCase,           value: lower_case  }
  - { key: readability-identifier-naming.VariableCase,           value: lower_case }
  - { key: readability-identifier-naming.PrivateMemberSuffix,    value: _          }
  - { key: readability-identifier-naming.ProtectedMemberSuffix,  value: _          }
  - { key: readability-identifier-naming.MacroDefinitionCase,    value: UPPER_CASE }
  - { key: readability-identifier-naming.EnumConstantCase,         value: CamelCase }
  - { key: readability-identifier-naming.EnumConstantPrefix,       value: _         }
  - { key: readability-identifier-naming.ConstexprVariableCase,    value: CamelCase }
  - { key: readability-identifier-naming.ConstexprVariablePrefix,  value: _         }
  - { key: readability-identifier-naming.GlobalConstantCase,       value: CamelCase }
  - { key: readability-identifier-naming.GlobalConstantPrefix,     value: _         }
  - { key: readability-identifier-naming.MemberConstantCase,       value: CamelCase }
  - { key: readability-identifier-naming.MemberConstantPrefix,     value: _         }
  - { key: readability-identifier-naming.StaticConstantCase,       value: CamelCase }
  - { key: readability-identifier-naming.StaticConstantPrefix,     value: _         }