Saplyn's Notebook
"What is this?"
This is a notebook for me, Saplyn. It acts as a personal knowledge index and cheatsheet. It's primarily intended for my own use. I'm publishing it simply because it makes it easier for me to access it when I'm away from my computer.
However, you may find it useful as well, and that's great! I'm happy to help. Also, if you find any errors or inaccuracies, it's welcome to open an issue or a pull request to correct it.
"What this isn't..."
- A comprehensive guide, tutorial, or documentation to anything;
- A complete registry or catalog of all things;
- An unopinionated or objective source of information;
- Or anything beyond a personal notebook.
About
This book is created using mdBook.
Understandings
Variable, Function, and Program
- Variable is the container of data.
- Function is the container of instructions.
- Program is the honestest way to instruct computers. It describes how states are manipulated, and how instructions are carried out.
Memory: Stack and Heap
Stack is a segment of memory that is used to store local variables and function call information of one thread. A new stack frame (a contiguous chunk of memory) is created (push) each time a function is called, and destroyed (pop) when returns. Values live on the stack become invalid when its stack frame is destroyed.
Heap is a pool of memory used for dynamic memory allocation. It allows us to allocate a block of memory of a given size and get a pointer to it, the allocated memory stays reserved until it is explicitly deallocated. Values live on the heap are valid until deallocated. The heap is shared among all threads.
C
C, a foundational pillar in the realm of computer programming. Developed in the early 1970s by Dennis Ritchie at Bell Labs, it was designed to provide a powerful and flexible tool for system programming, offering low-level access to memory and hardware while maintaining a high degree of portability.
Features
- Compiled
- Statically typed
- Low-level control
- Efficient
Sample Program (Guessing Game)
#include <stdio.h>
#include <stdlib.h>
int main() {
printf("Guess the number!\n");
int secret_number = (rand() % 100) + 1;
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;
}
Basic Syntax
Program Structure
// optional, when we need external functionality
#include <header>
#include "header.h"
// required, `main()` the entry of every c program.
int main() {
return 0;
}
Variables & Types
// variable
int age; // declaration
int score = 90; // declaration with initialization
float height = 1.75;
char initial = 'J';
// constant
const int MAX = 100;
const double PI = 3.14;
// array
char str[5]; // declaration
int numbers[] = {1, 2, 3, 4, 5}; // declaration with initialization
// basic types
int, float, double, char, void
// type modifiers
long, short, signed, unsigned
// type conversion
(int) 3.14; // => 3
(float) 3; // => 3.0
Operators
Note that when doing calculations, compiler will tries to promote the operands to the same type by converting "lower" type to "higher" type.
// arithmetic
+ - * / % ++ --
// `/` is integer division if both operands are integers.
// `%` is the remainder of the division.
// relational
== != > < >= <=
// logical
&& || !
// `&&` and `||` are short-circuit operators.
// bitwise
& | ^ ~ << >>
// assignment
= += -= *= /= %= <<= >>= &= |= ^=
// `x op= y` is equivalent to `x = x op y`
// conditional
?:
// `x ? y : z` is equivalent to `if (x) {y}; else {z};`
Control Flow
Branching
// if-else
if (condition) { /* ... */ }
else if (condition) { /* ... */ }
else { /* ... */ }
// switch-case
switch (enumerable) {
case variant1: /* ... */ break;
case variant2: /* ... */ break;
default: /* ... */
}
Looping
// while loop
while (condition) { /* ... */ }
// do-while loop
do { /* ... */ } while (condition);
// for loop
for (init; condition; update) { /* ... */ }
for (int i = 0; i < 10; i++) { /* ... */ }
// break, continue
break; // exits the loop
continue; // skips the current iteration.
Jumping
// label
label: // statement
// goto
goto label; // jumps to the label.
Functions
// declaration
int add(int a, int b);
// definition (implementation)
int add(int a, int b) {
return a + b;
}
// calling
int sum = add(3, 5);
Compound Data Types
Structures
Allowing to group different data types together.
// declaration
struct Point {
int x;
int y;
};
// construction
struct Point p1 = {0, 0};
// accessing members
p1.x = 3;
p1.y = 4;
Unions
Allowing storing different data types in the same memory location. With only one variant (member) being accessible at a time.
// declaration
union Data {
int i;
float f;
};
// construction
union Data data;
// accessing members
data.i = 10; // only `i` is accessible now
data.f = 220.5; // only `f` is accessible now
// note that accessing `i` after assigned `f` is undefined behavior.
Enumerations
Allowing to define a type with a set of named values.
// declaration
enum Color { RED, GREEN, BLUE };
// construction
enum Color c = BLUE;
// Using enum values
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]`
Const
Resources
Since C and C++ are closely related, the resources for C++ are also applicable to C. Here lists only the resources specific to C.
- C Standard Library: Standard library reference.
Libraries Memo
Standard Libraries
stdio.h
: standard input/outputstdlib.h
: standard library (memory allocation, random number generation, etc.)string.h
: string manipulationmath.h
: mathematical functionstime.h
: date and time functionsctype.h
: character handling functionsstdbool.h
: boolean type and values
Other Libraries
- SQLite: A small, fast, self-contained, high-reliability, full-featured, SQL database engine.
- fmt: A modern formatting library
C++
C++, or CPP, is an extension of the C programming language. Bjarne Stroustrup developed C++ in the early 1980s at Bell Labs, aiming to enhance C's capabilities with features such as object-oriented programming (OOP), generic programming, and improved type safety.
Features
- Compiled
- Statically typed
- Low-level control
- Efficient
- Support object-oriented diagram
Sample Program (Guessing Game)
#include <ctime>
#include <iostream>
#include <random>
int main() {
std::cout << "Guess the number!" << std::endl;
std::mt19937 rng(time(0));
int secret_number = (rng() % 100) + 1;
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;
}
"Plus"-ed Syntax
C++ is a superset of C, and nearly every valid C program is a valid C++ program. For this reason, please refer to the C section for the basic syntax of C++.
Types
// boolean
bool b = true; // native boolean type, without `cstdbool`
// auto typing (type inference)
auto x = 6; // x is an int
auto y = 3.14; // y is a double
// decltype typing (type inference)
int i = 114;
decltype(i) j = 514; // j is an int
// using type (type alias)
using Distance = double;
Distance d = 5.0; // d is a double
// nullptr
int* p = nullptr; // p is a pointer to int
// reference
int i = 1919;
int& r = i; // r is a reference to i
r = 810; // i is now 810, r is just another name for i
Namespaces
namespace lyn {
int number; // number is a member of namespace lyn (lyn::number)
}
int main() {
lyn::number = 32;
}
Functions
// default arguments
void print(int x, int y = 0) {
std::cout << x << " " << y << std::endl;
}
print(5); // => 5 0
print(5, 10); // => 5 10
// function overloading
int add(int x, int y) { return x + y; }
double add(double x, double y) { return x + y; }
add(1, 2); // => 3
add(1.1, 2.2); // => 3.3
// lambda
auto add = [/* captures */](int x, int y) { return x + y; };
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
Classes
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 pointes 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) {}
};
Resources
- C++ Standard Library: Standard library reference.
- Bjarne Stroustrup's homepage: Father of C++'s website.
Tools
- CMake: A cross-platform build system.
- Chocolatey: CMake on Chocolatey.
- vcpkg: A C++ library manager
Compilers
- MinGW-w64: Windows port of GCC.
- Chocolatey: MinGW-w64 on Chocolatey.
- MSYS2: A software distro that contains GCC.
Libraries Memo
Standard Libraries
iostream
: standard input/output streamstring
: string manipulationchrono
: date and time functionsvector
: dynamic arraymap
: associative array
Other Libraries
- SQLiteCpp (website): A smart and easy to use C++ SQLite3 wrapper
- fmt: A modern formatting library
- spdlog: Fast C++ logging library
- Dear ImGui: Bloat-free Immediate Mode Graphical User interface for C++ with minimal dependencies
Rust
Rust is a systems programming language that is performant, reliable, and productive. It is a multi-paradigm, general-purpose language that is safe and concurrent by design. Created by Graydon Hoare as a personal project in 2006 and later sponsored by Mozilla, it made its first stable release in 2015.
Features
- Compiled
- Statically typed
- Low-level control
- Efficient
- Memory safe
- Expressive
Sample Program (Guessing 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 main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(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;
}
}
}
}
Basic Syntax
Program Structure
// optional, when we need external functionality // notably, rust has a prelude that is used by default. use std::io; // required, `main()` the entry of every rust program. fn main() { println!("Hello, world!"); }
Variables & Types
// variable
// most of the time, rustc infers the type.
let score = 90; // immutable variable
let first_name: char; // type annotation, optional
let mut height = 1.75; // mutable variable
// constant (computable at compile time)
const MAX: i32 = 100; // requires type annotation
const PI: f64 = 3.14; // requires initialization too
// array
let numbers = [1, 2, 3, 4, 5];
let mut str: [char; 5];
// tuple
let tup = (500, 6.4, 1);
let tuple: (i32, f64, char) = (42, 6.28, 'J');
// shadowing
let x = 5; // immutable (5)
let x = x + 1; // shadowing, new variable (6)
let x = x * 2; // shadowing, another new variable (12)
// basic types
i8, i16, i32, i64, i128
u8, u16, u32, u64, u128
isize, usize // architecture dependent
f32, f64
char // unicode scalar value
bool
// type conversion
let x: i32 = 42;
let y: f64 = x as f64;
let z: i32 = y as i32;
Operators
// arithmetic
+ - * / %
// `/` is integer division if both operands are integers.
// `%` is the remainder of the division.
// relational
== != > < >= <=
// logical
&& || !
// `&&` and `||` are short-circuit operators.
// bitwise
& | ^ ! << >>
// assignment
= += -= *= /= %= <<= >>= &= |= ^=
Control Flow
Branching
Branching in Rust is more powerful than just this, for details, see the Pattern Matching chapter.
// if-else expression (can "return" values)
if condition { /* ... */ }
else if condition { /* ... */ }
else { /* ... */ }
// match expression (can "return" values)
match value {
pattern1 => /* ... */,
pattern2 => /* ... */,
_ => /* ... */, // catch-all (wildcard)
}
Looping
// loop expression (while true)
loop { /* ... */ }
// while loop
while condition { /* ... */ }
// for loop
for element in iterable { /* ... */ }
// range
1..=5 // 1, 2, 3, 4, 5
1..5 // 1, 2, 3, 4
// break and continue
loop {
if condition { break; }
if condition { continue; }
}
// the `loop` can be labeled and `break` to the label.
'outer: loop {
'inner: loop {
break 'outer;
}
}
// it can also `break` with a value
let result = loop {
if condition { break 1; }
else { break 2; }
};
Functions
This is only a brief introduction to functions and closures. For details, see the Function chapter.
// function
// fn name(param: type) -> return_type { /* ... */ }
fn add(a: i32, b: i32) -> i32 {
a + b // last expression implicitly returned
}
let sum = add(3, 5);
// closure (anonymous function)
let closure = |a: i32, b: i32| -> i32 { a + b }; // functions are values.
let add = |a, b| a + b; // type is inferred.
let sum = add(3, 5);
Structs
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;
Enums
// 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 (like structs)
impl AppError {
fn is_io_error(&self) -> bool {
match self {
AppError::IoError(_) => true,
_ => false,
}
}
}
Fundamentals
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![]
Macros are powerful, for in-depth understanding and usage, see the Macros chapter. Here lists some commonly used macros.
// 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)
}
Generic, Trait & Lifetime
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. For in-depth understanding, see Understanding Lifetimes
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;
// 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();
}
Pattern Matching
Destructuring
// 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();
match
Expression
// 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),
}
if let
and let else
if let Some(x) = compute() {
println!("got a number: {}", x);
} else {
println!("got nothing");
}
let x = Some(5) else {
println!("got nothing");
};
while let
Loop
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop() {
println!("{}", top);
}
Function
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 = 10;
let drop_x = || { drop (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);
Macros
This page is INCOMPLETE
This page is incomplete and is still being written.
Introduction
Macros are a way to write code that writes other code. The term macro refers
to a family of features in Rust: declarative macros with macro_rules!
and
three kinds of procedural macros:
- Custom
#[derive]
macros that specify code added with the derive attribute used on structs and enums - Attribute-like macros that define custom attributes usable on any item
- Function-like macros that look like function calls but operate on the tokens specified as their argument
Declarative Macros
Custom #[derive]
Macros
Attribute-like Macros
Function-like Macros
Async
This page is INCOMPLETE
This page is incomplete and is still being written.
Future
Trait
Pin
& Unpin
Concepts
Understanding Lifetimes
When a reference with a lifetime is used, the borrow checker tracks the flow of where it starts to where it's used, and checks along the way to ensure that no conflict happens.
Lifetime often coincide with a scope, but they don't have to, the
following code demonstrates this: The lifetime 'a
is alive up until
the end of the code block, however the if
branch needs a &mut x
to
mutate x
. This code compiles because &'a x
and &mut x
don't cause
conflict, as they are in different flows.
let mut x = 114; // #
let r = &x; // ├───── &'a x
if condition { // ├──┐
x = 514; // │ └── &mut x
} else { // │
println!("{}", r); // └───── &'a x
}
Variance: Co/In/Contra-variant
Variance is a concept in type theory, it describes what types are subtypes of
other types, and when a subtype can be used in place of a supertype (and vice
versa). Specifically, if 'b: 'a
('b
outlives 'a
), then 'b
is a subtype
of 'a
. There are three kinds of variance: covariant, invariant, and
contravariant. For detailed in-depth reference, see
The Rust Reference
and The Rustonomicon.
A type is covariant if we can use a subtype in place of the type.
// covariant in 'short
fn covariant<'short>(s: &'short str);
let valid: &'static str;
let valid: &'longer str; // 'longer: 'short ('longer outlives 'short)
// covariant in T
fn covariant<'short>(v: &Vec<&'short str>);
let valid: &Vec<&'longer str>; // 'longer: 'short
/*
'short ----
'longer ----------------
'static --------------------------------
*/
A type is invariant if we must provide exactly the same type.
fn invariant<'exact>(
v: &mut Vec<&'exact str>
str: &'exact str
) {
v.push(str);
}
// will otherwise allow short-lived str be seen as 'static
let invalid: &mut Vec<&'static str>;
A type is contravariant if we can use a supertype in place of the type.
fn contravariant(s: &'static str); // strict, accepts fewer types
fn contravariant<'short>(s: &'short str); // flexible, accepts more types
let longer: &'static str; // more useful, lives longer
let short: &'short str; // less useful, lives shorter
Resources
Official Toolchain
- rustup: Rust toolchain installer.
- cargo: Rust's build system and package manager.
- clippy: A collection of lints to catch common mistakes and improve Rust code.
- rustfmt: A tool for formatting Rust code according to style guidelines.
- miri (nightly): An interpreter for Rust's mid-level intermediate representation.
Official Books
- The Rust Programming Language: The official book on Rust.
- The Rust Reference: The official reference for Rust.
- The Rustonomicon: The dark arts of advanced and unsafe Rust programming.
- Rust by Example: A collection of runnable examples that illustrate various Rust concepts and standard libraries.
- Async Programming in Rust (incomplete): A book on async programming in Rust.
IDE Support
- rust-analyzer: A modular compiler frontend for the Rust language, an LSP.
- CodeLLDB: A native debugger extension for VSCode based on LLDB.
- RustRover (paid): JetBrains' dedicated Rust IDE.
Cargo Plugins
- cargo-audit: Audit
Cargo.lock
for crates with security vulnerabilities. - cargo-expand: Shows the result of macro expansion and
#[derive]
expansion. - cargo-generate: Bootstrap a new project by leveraging a pre-existing git repository as a template.
- cargo-nextest: A next-generation testing tool for Rust.
- cargo-update: A cargo subcommand for checking and applying updates to installed executables.
- cargo-watch: Watches over your Cargo project’s source and runs cargo build when files change.
Books, Articles & Blog Posts
- Awesome Rust: A curated list of Rust code and resources.
- The Little Book of Rust Macros: A book on Rust macros.
- Rust for Rustaceans: The book to read after reading The Book.
- Rust Web Framework Comparison: A comparison of some web frameworks and libs written in Rust.
- Pin, Unpin, and why Rust needs them: A blog post about
Pin
andUnpin
in Rust. - Structuring, testing and debugging procedural macro crates: A blog post about writing procedural macros in Rust.
- Pin and suffering
Crates Memo
- crates.io: The Rust community’s crate registry.
- docs.rs: The Rust community’s documentation hosting service.
- lib.rs: A community maintained catalog of Rust crates and programs.
- Rust Standard Library: The Rust Standard Library API documentation.
Quality of Life
- thiserror:
#[derive(Error)]
for defining custom error types. - anyhow: Flexible concrete Error type built on
std::error::Error
. - simplelog: Simple and easy-to-use logging facility.
- fake: An easy to use library for generating fake data.
Ex-Std
- rand: Random number generators and other randomness functionality.
- chrono: Date and time library.
- time: Date and time library.
- log: A lightweight logging facade.
Proc Macros
- syn: Parser for Rust source code.
- quote: Quasi-quoting macro
quote!{}
. - proc-macro2: A substitute implementation of the compiler’s
proc_macro
API. - trybuild: Test harness for ui tests of compiler diagnostics
Async
- tokio (website): An asynchronous runtime for the Rust programming language.
- smol: A small and fast async runtime.
- futures: Foundations and utilities for asynchronous programming.
- futures-lite: A lightweight async prelude.
Encoding
- serde (website): A generic serialization/deserialization framework.
- serde_json: JSON support for Serde.
Rocket
- rocket (website): Web framework for Rust.
- rocket_ws: WebSockets support for Rocket.
- rocket_db_pools: Rocket async database pooling support.
- rocket_cors: Cross-origin resource sharing support for Rocket.
Shell Script
Shell script is a programming language that is used to automate tasks, such as
running commands, moving files, and so on. It is a scripting language that is
interpreted by the shell, such as bash
, zsh
and sh
.
Features
- Interpreted
- Dynamically-typed
- Scripting
- Automation
Sample Program (Hello World)
#!/bin/bash
echo "Hello, World!"
Basic Syntax
Script Structure
#!/bin/bash
# above is shebang, the path to the interpreter
# commands to execute
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
Resources
IDE Support
- Bash Language Server: A language server for Bash
- ShellCheck: A static analysis tool for shell scripts
Go
Go is a statically typed, compiled programming language designed at Google. It's simple by design, and it's a great language for writing concurrent programs.
Features
- Compiled
- Statically typed
- Garbage-collected
- Concurrent
- Supports object-oriented diagram
Sample Program (Hello World)
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
Basic Syntax
Package Structure
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
Variables & 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
+ - * / % ++ --
// `/` is integer division if both operands are integers.
// `%` is the remainder of the division.
// relational
== != > < >= <=
// logical
&& || !
// `&&` and `||` are short-circuit operators.
// bitwise
& | ^ << >>
&^ // bit clear (AND NOT)
// assignment
= += -= *= /= %= <<= >>= &= |= ^=
Control Flow
Branching
// 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.")
}
Looping
// 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
A defer
statement defers the execution of a function until the surrounding
function returns.
// 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
Functions
// declaration
func add(a int, b int) int {
return a + b
}
// call
add(1, 2)
// multiple return values
func swap(a int, b int) (int, int) {
return b, a
}
func map(str []string, f func(string) string) []string {
result := make([]string, len(str))
for i, v := range str {
result[i] = f(v)
}
return result
}
// named return values
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
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")
Essentials
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)
}
}
}
Resources
Official Documentation
Packages Memo
Java
Java is a high-level, class-based, object-oriented programming language that is designed to have as few implementation dependencies as possible.
Features
- Object-oriented
- Garbage-collected
- Statically typed
- Platform-independent by runtime
- Compiled and interpreted
Sample Program (Hello World)
// hello world in Java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello world!");
}
}
Basic Syntax
Program Structure
In Java, a program is made up of one or more classes, each file is a class.
The class with the main
method acts as the entry point of the program.
// HelloWorld.java
public class HelloWorld {
// other methods
public static void sayHello() { /* ... */ }
// main method
public static void main(String[] args) {
System.out.println("Hello world!");
}
}
Variables & Types
// variable
int score = 90;
char first_name;
double height = 1.75;
// constant
final int MAX = 100;
final double PI = 3.14;
// array
int[] numbers = {1, 2, 3, 4, 5};
char[] str = new char[5];
Operators
// arithmetic
+ - * / % ++ --
// relational
== != > < >= <=
// bitwise
& | ^ ~ << >>
>>> // Unsigned right shift
// logical
&& || !
// `&&` and `||` are short-circuit operators.
// assignment
= += -= *= /= %=
Control Flow
Branching
if (condition) {
// ...
} else if (condition) {
// ...
} else {
// ...
}
switch (expression) {
case value1:
// ...
break;
case value2:
// ...
break;
default:
// ...
}
Looping
// 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);
}
}
Functions
// declaration
int add(int num1, int num2) {
int sum = num1 + num2;
return sum;
}
add(3, 5); // call
// lambda
Function<Integer, Integer> add = (a, b) -> a + b;
add.apply(3, 5);
Classes
Class as Object
In Java, every item has an access specifier:
public
: accessible from anywhereprotected
: 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;
}
Python
Python is a high-level, interpreted programming language. It is known for its simplicity and readability. Python is a great language for beginners and is widely used in data science and machine learning.
Features
- Interpreted
- Dynamically typed
- Easy to learn
Sample Program (Hello World)
# hello world in Python
print("Hello world!")
Basic Syntax
Variables
# variable
score = 90
name = "Saplyn"
MAX = 100
# string
name = "Saplyn"
desc = """
he's a
very very
long
cat
"""
fstr = f"Hello, {name}!"
Operators
# arithmetic
+ - * / % // **
# `/` is float division
# `//` is floor division
# `**` is exponentiation
# comparison
== != < > <= >=
# logical
and or not
# assignment
= += -= *= /= %= //= **=
Flow Control
Branching
# if-else
if age < 12:
print("Kitty!")
elif age < 18:
print("Cat!")
else:
print("Adult Cat!")
# simple if-else
print("Kitty!") if age < 12 else print("Cat!")
Looping
# while
i = 0
while i < 5:
print(i)
i += 1
# for
for i in range(5):
print(i)
for i in range(1, 5):
print(i)
fruits = ['apple', 'banana', 'orange']
for fruit in fruits:
print(fruit)
# loop-else
# code inside the else block will be executed only if the loop completes all
# iterations without any break statement being encountered.
i = 1
while i <= 5:
print(f"loop: {i}")
i += 1
else:
print("Loop completed without any breaks")
# break
i = 0
while True:
i += 1
if i > 5:
break
# continue
for i in range(5):
if i % 2 == 0:
continue
print(i)
Functions
# definition
def add(a, b=1):
return a + b
add(3, 4) # => 7
add(3) # => 4
add(b=3, a=4) # => 7, keyword arguments
# arbitrary arguments
def add(*args):
return sum(args)
add(1, 2, 3, 4, 5) # => 15
# arbitrary keyword arguments
def display(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
display(name="Saplyn", age=18) # => name: Saplyn, age: 18
Lists, Tuples & Dictionaries
# list
numbers = [1, 2, 3, 4, 5]
numbers[0] # => 1
numbers[-1] # => 5, backward index
numbers[1:3] # => [2, 3], slicing
numbers[::2] # => [1, 3, 5], step slicing
# tuple (immutable)
normal_tuple = (1, 2, 4, 8, 16)
single_tuple = 1,
empty_tuple = ()
# dictionary (map)
person = {
"name": "Saplyn",
"age": 18,
"height": 1.75
}
person["name"] # => "Saplyn"
person["age"] # => 18
person["height"]# => 1.75
Haskell
Haskell is a purely functional programming language that is declarative and statically typed. It was named after the logician Haskell Curry. Haskell's main implementation is the Glasgow Haskell Compiler (GHC).
Features
- Statically typed
- Purely functional
- Lazy evaluation
Basic Syntax
Values, Functions & Types
Haskell is a purely functional programming language, which means that every variable is immutable (value) and every function is a first-class citizen.
-- values
x :: Int -- declare x as an Int
x = 1 -- bind 1 to x
y = 2 -- bind 2 to y (type inferred)
z = x + y -- bind (x + y) to z
-- functions
add :: Int -> Int -> Int -- declare `add` as a function from Int to Int to Int
-- (can be seen as "take 2 Ints and return an Int")
add x y = x + y -- declare `add x + y` as a `x + y`
add 1 2 -- 3
-- types
Int -- integer, fixed-precision integer
Integer -- integer, arbitrary-precision integer
Float -- floating-point number, single-precision
Double -- floating-point number, double-precision
Bool -- boolean, either True or False
Char -- character, single unicode character
[a] -- list, a sequence of elements of type `a`
(a, b) -- tuple, a pair of elements of type `a` and `b`
Operators
-- arithmetic
+ - * -- (binary, infix)
div -- (binary, prefix)
-- comparison (binary, infix)
== /= < <= > >=
-- logical
&& || -- (binary, infix)
not -- (unary, prefix)
-- infix marker
4 `add` 2 -- add 4 2
4 `div` 2 -- div 4 2
-- function application
($) :: (a -> b) -> a -> b
($) f x = f x
$ add 1 2 -- 3
-- function composition
(.) :: (b -> c) -> (a -> b) -> a -> c
(.) f g x = f (g x)
descSort = reverse . sort
List
Unlike imperative programming languages, List in Haskell is considered to be a primary data structure, and one of the most important and widely used types.
-- list of Ints
list = [1, 2, 3]
-- `:` put an element at the beginning of a list
list' = 4 : 5 : 6 : []
-- `++` concatenate two lists
list'' = list ++ list' ++ [7, 8, 9]
To work with lists, Haskell provides a set of useful functions:
head [1, 2, 3, 4] -- 1
tail [1, 2, 3, 4] -- [2, 3, 4]
init [1, 2, 3, 4] -- [1, 2, 3]
last [1, 2, 3, 4] -- 4
length [1, 2, 3, 4] -- 4
null [] -- True
Flow Control
Branching
-- multiple bindings
isZero :: Int -> Bool
isZero 0 = True
isZero _ = False -- `_` wildcard, matches anything
-- if-then-else
max :: Int -> Int -> Int
max x y =
if x > y then
x
else
y
Recursion
In a pure functional language like Haskell, there is no way to change the value, making loops impossible. Instead, recursion is used to replace its functionality.
-- factorial
factorial :: Int -> Int
factorial 0 = 1 -- base case, also termination condition
factorial n = n * factorial (n - 1) -- recursive case
-- fibonacci
fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)
Custom Types
In Haskell, we can define our own types using type
and data
keywords.
-- type synonym
type Name = String
type Age = Int
-- data type
data Color = Red | Green | Blue -- different constructors
data Calculation =
Add Int Int -- constructor with parameters
| Sub Int Int
| Mul Int Int
| Div Int Int
calc :: Calculation -> Int
calc (Add x y) = x + y
calc (Sub x y) = x - y
calc (Mul x y) = x * y
calc (Div x y) = x `div` y
-- recursive data type
data PeaNum = Succ PeaNum | Zero
four :: PeaNum
four = Succ $ Succ $ Succ $ Zero
data Tree a = Leaf | Node (Tree a) a (Tree a)
tree :: Tree Int
tree =
Node (Node Leaf 1 Leaf) 2 (Node (Node Leaf 3 Leaf) 4 Leaf)
Function
Guards
Guards are a way to define a function by pattern matching on the input arguments. They are defined by a series of boolean expressions.
max' :: Int -> Int -> Int
max' x y
| x > y = x
| otherwise = y
fac n
| n <= 1 = 1
| otherwise = n * fac (n-1)
let
and where
In functions, let
and where
can be used to declare local expression aliases
in a function.
inRange l r x =
let in_lower_bound = l <= x
in_upper_bound = r >= x
in
in_lower_bound && in_upper_bound
inRange l r x =
in_lower_bound && in_upper_bound
where
in_lower_bound = l <= x
in_upper_bound = r >= x
Accumulator
Accumulator is a common pattern in functional programming to avoid stack overflow when using recursion. It's also known as tail recursion.
fac n = aux n 1 -- `aux` is a helper function
where
aux n acc -- `aux` is declared here, and `acc` is the accumulator
| n <= 1 = acc
| otherwise = aux (n - 1) (n * acc)
Higher Order Function
A higher order function is a function that takes a function as an argument or returns a function as a result.
app :: (a -> b) -> a -> b -- `app` takes a function `a -> b` and an argument `a`
app f x = f x -- and returns a result `b`
add1 x = x + 1
app add1 1 -- 2
Lambda Function
Lambda function is an anonymous function that is created using the \
symbol.
(\x -> x + 1) 1 -- 2
(\x y -> x + y) 1 2 -- 3
-- as first-class citizens, lambda functions can be bind to a name
add = \x y -> x + y
Currying
In Haskell, any function can be rewritten to take only one argument:
-- the function that takes 3 args
f :: a -> b -> c -> d
-- can be written as:
f :: a -> (b -> (c -> d))
-- a func that takes 1 arg and returns a func that takes 1 arg
-- which returns a func that takes 1 arg and returns the result
Partial Application
Partial application is the process of supplying a function with fewer arguments than it requires, resulting in a new function that takes the remaining arguments.
add :: Int -> Int -> Int
add = (\x -> (\y -> x + y))
map :: (a -> b) -> [a] -> [b]
doubleList :: [Int] -> [Int]
doubleList = map (\x -> x * 2)
Folding
Folding is a way to reduce a list to a single value. It takes a binary function,
a starting value, and a list to fold up. In Haskell, there are two folding
functions: foldl
and foldr
.
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr (+) 0 [1, 2, 3] -- 1 + (2 + (3 + 0)) = 6
sum = foldr (+) 0
and = foldr (&&) True
or = foldr (||) False
length = foldr (\x -> (+) 1) 0
length = foldr (const $ (+) 1) 0
map f = foldr ((:) . f) []
-- https://wiki.haskell.org/Fold#List_folds_as_structural_transformations
foldr (\x acc -> <term>) <start_acc> <list>
foldl (\acc x -> <term>) <start_acc> <list>
Unsorted Notes
List Comprehension
List comprehension is a concise way to generate lists in Haskell.
[ <gen> | <elem> <- <list>, ..., <guard>, ...]
[ 2*x | x <- [1, 2, 3] ] => [2, 4, 6]
[ 2*x | x <- [1, 2, 3], x > 1 ] => [4, 6]
[ (x,y) | x <- [1, 2, 3], y <- ['a', 'b'] ]
=> [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b'), (3, 'a'), (3, 'a')]
JavaScript
JavaScript is a programming language used to make websites interactive, allowing web pages to respond to user actions and dynamically update content. It's initially designed to run in web browsers, but with the introduction of Node.js, it can now run on nearly any platform.
Features
- Interpreted
- Dynamically-typed
Sample Program (Hello World)
console.log('Hello, world!');
Basic Syntax
Variables & Types
// variable
var score = 90; // old-fashioned
let name = 'Saplyn'; // preferred in modern JS
// var vs let: https://stackoverflow.com/questions/762011/what-is-the-difference-between-let-and-var
// constant
const PI = 3.14; // cannot be reassigned
// array (dictionary, associative array)
let numbers = [1, 2, 3, 4, 5];
let names = ['Saplyn', 'Moxie', 'Nyx'];
let mixed = [1, 'Saplyn', true, { name: 'Moxie' }];
// basic types
number, string, boolean
null // absence of value
undefined // uninitialized variable
symbol // guaranteed unique value type
// symbol: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
Operators
// arithmetic
+ - * / % ++ --
// `/` is floating-point division
// `%` is the remainder of the division
// relational
== != // loss equality
=== !== // strict equality
>= <= > <
// logical
&& || !
// `&&` and `||` are short-circuit operators.
// bitwise
& | ^ ~ << >> >>>
// assignment
= += -= *= /= %=
Control Flow
Branching
// if-else statement
if (score >= 90) {
console.log('A');
} else if (score >= 80) {
console.log('B');
} else {
console.log('C');
}
// switch statement
switch (score) {
case 90:
console.log('A');
break;
case 80:
console.log('B');
break;
default:
console.log('C');
}
// ternary operator
let result = (score >= 60) ? 'Pass' : 'Fail';
// nullish coalescing operator
let name = null;
let username = name ?? 'Anonymous';
Looping
// while loop
let i = 0;
while (i < 5) {
console.log(i);
i++;
}
// do-while loop
let j = 0;
do {
console.log(j);
j++;
} while (j < 5);
// for loop
for (let k = 0; k < 5; k++) {
console.log(k);
}
// for-in loop (iterate over object properties)
let person = { name: 'Saplyn', age: 25 };
for (let key in person) {
console.log(key, person[key]);
}
// for-of loop (iterate over iterable objects)
let numbers = [1, 2, 3, 4, 5];
for (let number of numbers) {
console.log(number);
}
break // exit the loop
continue // skip the rest of the loop
Functions
// function declaration
function add(a, b) {
return a + b;
}
let sum = add(3, 5);
// function expression (function as value)
let greet = function(name) {
console.log(`Hello, ${name}!`);
};
greet('Saplyn');
// arrow function (ES6)
let greet = (name) => {
console.log(`Hello, ${name}!`);
};
greet('Saplyn');
Resources
Runtime
- Node.js: An open-source, cross-platform JavaScript runtime.
- Deno: A most productive, secure, and performant JavaScript runtime.
- Bun: A fast all-in-one JavaScript runtime.
Package Manager & Registry
- nvm: Node version manager, a bash script to manage multiple active Node.js versions.
- npm: A package manager and registry for JavaScript.
- pnpm: A fast, disk space efficient package manager.
- yarn: A package manager that doubles down as project manager.
- jsr: A modern package registry for JavaScript and TypeScript.
TypeScript
TypeScript is JavaScript with syntax for types. It is a superset of JavaScript, and was also built on top of JavaScript.
Typed JS
TypeScript is a superset of JavaScript, and it not possible to learn TypeScript without learning JavaScript. For this reason, please refer to the JavaScript section for the basics.
Type Annotations
// typing variables
let n: number = 1;
let s: string = "hello";
let b: boolean = true;
let a: number[] = [1, 2, 3];
let o: {
name: string,
age: number
} = { name: "John", age: 30 };
// special types
let n: null = null;
let u: undefined = undefined;
// any type, opt out of type checking
let a: any = 1;
// typing functions
function add(a: number, b: number): number {
return a + b;
}
Vue
Vue is a JavaScript framework for building user interfaces. It builds on top of standard HTML, CSS, and JavaScript and provides a declarative and component-based programming model that helps us efficiently develop user interfaces.
Basics
Vue Component
In most build-tool-enabled Vue projects, we author Vue components using an
HTML-like file format called Single-File Component, .vue
. A Vue SFC consists
of three parts:
<template>
: the HTML template<script>
: the JavaScript logic<style>
: the CSS styles
<!-- https://vuejs.org/guide/introduction.html#single-file-components -->
<template>
<button @click="count++">Count is: {{ count }}</button>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<style scoped>
button {
font-weight: bold;
}
</style>
Reactivity
Vue's reactivity is achieved by using the ref
and reactive
functions. ref
is used to create a reactive reference to a value, and reactive
is used to
create a reactive object.
<!-- https://vuejs.org/tutorial/#step-2 -->
<template>
<!-- Mustache for templating -->
<h1>{{ message }}</h1>
<p>Count is: {{ counter.count }}</p>
</template>
<script setup>
import { reactive, ref } from 'vue'
const counter = reactive({ count: 0 })
const message = ref('Hello World!')
</script>
Reactive properties can be computed using the computed
function.
<template>
<p>Count is: {{ count }}</p>
<p>Double is: {{ doubleCount }}</p>
</template>
<script setup>
import { computed, ref } from 'vue'
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
</script>
Reactive properties can be watched using the watch
function.
<template>
<p>Count is: {{ count }}</p>
</template>
<script setup>
import { ref, watch } from 'vue'
const count = ref(0)
watch(count, (newValue, oldValue) => {
console.log(`count changed from ${oldValue} to ${newValue}`)
})
</script>
V-Attributes
v-bind:attr = "expr"
(:attr = "expr"
): binds an attribute to an expressionv-on:event = "handler"
(@event = "handler"
): binds an event listener to a handlerv-model
: two-way binding for form input elements
<template>
<p :class="active ? 'text-red-500' : 'text-black'">{{ text }}</p>
<button @click="handler">click me</button>
<input v-model="input" />
</template>
<script setup>
import { ref } from 'vue';
const active = ref(false);
function handler() {
active.value = !active.value;
}
const text = ref('');
</script>
v-if="expr"
,v-else-if="expr"
,v-else
: conditional renders elementsv-show="expr"
: toggles the visibility of an element
<template>
<div v-if="condA">Hello</div>
<div v-else-if="condB">World</div>
<div v-else>!</div>
<div v-show="condC">Visible</div>
</template>
<script setup>
import { ref } from 'vue';
const condA = ref(false);
const condB = ref(false);
const condC = ref(false);
</script>
v-for="item in items"
,v-for="(item, index) in items
: renders a list of items
<template>
<ul>
<li v-for="item in items" :key="item.id">{{ item.text }}</li>
</ul>
</template>
<script setup>
import { ref } from 'vue';
const items = ref([
{ id: 1, text: 'Hello' },
{ id: 2, text: 'World' },
]);
</script>
Component
Props
A child component can accept input from the parent via props.
<!-- https://vuejs.org/tutorial/#step-12 -->
<!-- Child.vue -->
<template>
<h2>{{ msg || 'No props passed yet' }}</h2>
</template>
<script setup>
const props = defineProps({
msg: String
})
</script>
<!-- Parent.vue -->
<template>
<Child :msg="greeting" />
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const greeting = ref('Hello from parent')
</script>
Emits
A child component can emit events to the parent.
<!-- https://vuejs.org/tutorial/#step-13 -->
<!-- Child.vue -->
<template>
<h2>Child component</h2>
</template>
<script setup>
const emit = defineEmits(['response'])
emit('response', 'hello from child')
</script>
<!-- Parent.vue -->
<template>
<Child @response="(msg) => childMsg = msg" />
<p>{{ childMsg }}</p>
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const childMsg = ref('No child msg yet')
</script>
Slots
A parent component can pass content to a child component via slots.
<!-- https://vuejs.org/tutorial/#step-14 -->
<!-- Child.vue -->
<template>
<slot>Fallback content</slot>
</template>
<!-- Parent.vue -->
<template>
<ChildComp>Message: {{ msg }}</ChildComp>
</template>
<script setup>
import { ref } from 'vue'
import ChildComp from './ChildComp.vue'
const msg = ref('from parent')
</script>
Resources
Official Tooling
- Vue.js: Vue official website
- Vue Router: The official Router for Vue.js
- Pinia: The official state management for Vue.js
- Vite: The official build tool for Vue.js
- IDE Support: Vue.js official IDE support
Community
- PrimeVue: A rich set of UI components for Vue.js
- VueUse: A collection of Vue composition utilities
- Nuxt.js: An intuitive Vue framework
Flutter
Dart
This page is INCOMPLETE
This page is incomplete and is still being written.
Program Structure
// required, `main()` the entry of every dart program.
void main() {
print('Hello, world!');
}
Variables & Types
// variable
int score = 90;
String name = 'John';
// type inference
var year = 1998;
var height = 1.75;
var map = {
'name': 'John',
'age': 23,
};
// null safety
int? age = null; // nullable
int age = 23; // non-nullable
// objects
List<int> numbers = [1, 2, 3];
Map<String, int> dict = {
'one': 1,
'two': 2,
};
Operators
// TODO
Control Flow
// if statement
if (cond) { /* ... */ }
else if (cond) { /* ... */ }
else { /* ... */ }
// c-style for loop
for (var i = 0; i < 10; i++) { /* ... */ }
// for-in loop
for (var item in items) { /* ... */ }
// while loop
while (cond) { /* ... */ }
Functions
// function
int add(int a, int b) {
return a + b;
}
add(1, 2); // => 3
// arrow function
int minus(int a, int b) => a - b;
minus(3, 2); // => 1
Dart Classes
This page is INCOMPLETE
This page is incomplete and is still being written.
Classes
class Cat {
String name;
int age;
// constructor
Cat(this.name, this.age);
// named constructor
Cat.fromName(String name) {
this.name = name;
}
// method
void meow() {
print('Meow!');
}
}
Inheritance
class Animal {
String name;
int age;
Animal(this.name, this.age);
void eat() {
print('$name is eating.');
}
}
class Cat extends Animal {
// propagate constructor
Cat(String name, int age) : super(name, age);
void meow() {
print('Meow!');
}
}
Mixins
SQL Server
SQL Server is a relational database management system (RDBMS) developed by Microsoft.
Features
- Relational
- ACID compliant
Basics
CRUD
Create
CREATE TABLE [person] (
[person_id] bigint PRIMARY KEY NOT NULL,
[name] varchar(255) NOT NULL
);
INSERT INTO [person] ([person_id], [name])
VALUES
(1, 'John'),
(2, 'Jane');
CREATE INDEX [idx_person_name]
ON person (name);
Read
SELECT * FROM [person];
SELECT [name]
FROM [person]
WHERE [name] = 'Jane';
SELECT count(*) AS [person_count]
FROM [person];
Update
ALTER TABLE [person]
ADD [last_name] varchar(255) NULL;
UPDATE [person]
SET [last_name] = 'Doe'
WHERE [name] = 'Jane';
ALTER TABLE [person]
DROP COLUMN [last_name];
Delete
DELETE FROM [person]
WHERE [name] = 'Jane';
DROP TABLE [person];
Using Database
USE [database_name];
Querying
WHERE
Condition
-- Comparison
SELECT * FROM [person] WHERE [name] <> 'John';
SELECT * FROM [person] WHERE [age] > 18;
-- `NULL` Check
SELECT * FROM [person] WHERE [last_name] IS NULL;
-- `OR`, `AND` & `NOT`
SELECT * FROM [person] WHERE [name] = 'John' OR [name] = 'Jane';
SELECT * FROM [person] WHERE [name] = 'John' AND [last_name] = 'Doe';
SELECT * FROM [person] WHERE NOT [name] = 'John';
-- `LIKE` Pattern
SELECT * FROM [person] WHERE [name] LIKE 'J%'; -- %: any string
SELECT * FROM [person] WHERE [name] LIKE 'J_n_'; -- _: any character
SELECT * FROM [person] WHERE [name] LIKE 'J[ae]n'; -- []: any character in the set
-- `IN` & `NOT IN`
SELECT * FROM [person] WHERE [name] IN ('John', 'Jane');
-- `BETWEEN AND`
SELECT * FROM [person] WHERE [age] BETWEEN 18 AND 30;
Refining Results
-- `ORDER BY` `ASC` & `DESC`
SELECT * FROM [person] ORDER BY [name] ASC;
SELECT * FROM [person] ORDER BY [name] DESC;
-- `LIMIT`
SELECT * FROM [person] LIMIT 10;
-- `DISTINCT`
SELECT DISTINCT [name] FROM [person];
-- `TOP` & `WITH TIES`
SELECT TOP 1 * FROM [person];
SELECT TOP 1 WITH TIES * FROM [person] ORDER BY [age] ASC;
SELECT TOP 10 PERCENT * FROM [person] ORDER BY [age] ASC;
-- `GROUP BY`
-- Note: columns not in `GROUP BY` must be in an aggregate function.
SELECT [name], AVG([age]) AS [avg_age]
FROM [person]
GROUP BY [name];
-- `HAVING`
SELECT [name], AVG([age]) AS [avg_age]
FROM [person]
GROUP BY [name]
HAVING COUNT(*) > 1;
Joining Tables
-- `INNER JOIN`
SELECT * FROM [person]
INNER JOIN [address]
ON [person].[person_id] = [address].[person_id];
-- `LEFT JOIN` preserves all left table's rows, leaving `NULL` for unmatched rows.
-- `RIGHT JOIN` is the opposite.
SELECT * FROM [person]
LEFT JOIN [address]
ON [person].[person_id] = [address].[person_id];
-- `FULL JOIN`
-- Preserving all rows from both tables.
SELECT * FROM [person]
FULL JOIN [address]
ON [person].[person_id] = [address].[person_id];
-- `CROSS JOIN`
-- Cartesian product of two tables.
SELECT * FROM [person]
CROSS JOIN [address];
-- Self `JOIN`
-- Joining a table with itself.
SELECT * FROM [person] AS [p1]
INNER JOIN [person] AS [p2]
ON [p1].[person_id] = [p2].[person_id];
-- Chaining `JOIN`
SELECT * FROM [person]
INNER JOIN [address] ON [person].[person_id] = [address].[person_id]
INNER JOIN [phone] ON [person].[person_id] = [phone].[person_id];
Subqueries
-- Basic Subquery (inner query auto captures the outer query's columns)
SELECT
[stu_id],
[crs_id],
[grade]
FROM [grades]
WHERE [grade] > (
SELECT AVG([grades_sub].[grade]) AS [grade_avg]
FROM [Grades] AS [grades_sub]
WHERE [grades_sub].[stu_id] = [stu_id]
);
-- `ANY`/`SOME`: `true` if any subquery values meet the condition.
SELECT [product_name]
FROM [products]
WHERE [product_id] = ANY (
SELECT [product_id]
FROM [order_details]
WHERE [quantity] = 10
);
-- `ALL`: `true` if every subquery values meet the condition.
SELECT [product_name]
FROM [products]
WHERE [product_id] = ALL (
SELECT [product_id]
FROM [order_details]
WHERE [quantity] = 10
);
-- `IN` & `NOT IN`
SELECT [supplier_name]
FROM [suppliers]
WHERE [supplier_id] IN (
SELECT [supplier_id]
FROM [products]
WHERE [price] > 50
);
-- `EXISTS` & `NOT EXISTS`
SELECT [supplier_name]
FROM [suppliers]
WHERE EXISTS (
SELECT [product_name]
FROM [products]
WHERE [products].[supplier_id] = [suppliers].[supplier_id] AND [price] < 20
);
-- Relational Division
-- select students who selected all the courses
SELECT -- projection
[students].[name],
[students].[id]
FROM [students]
WHERE NOT EXISTS( -- divisor
SELECT *
FROM [courses]
WHERE NOT EXISTS( -- dividend
SELECT *
FROM [enrollments]
WHERE [enrollments].[crs_id] = [courses].[id]
AND [enrollments].[stu_id] = [students].[id]
)
);
INSERT
With SELECT
-- `INSERT INTO SELECT FROM`
INSERT INTO [customers] ([customer_name], [city], [country])
SELECT [supplier_name], [city], [country]
FROM [suppliers];
-- `SELECT INTO`
SELECT [id], [name], [email]
INTO [employees_new]
FROM [users]
WHERE [id] > 200;
Integrity
Constraints
-- `PRIMARY KEY`
ALTER TABLE [users]
ADD CONSTRAINT [pk_users_id]
PRIMARY KEY ([id]);
-- `FOREIGN KEY`
ALTER TABLE [orders]
ADD CONSTRAINT [fk_orders_customer_id]
FOREIGN KEY ([customer_id]) REFERENCES [customers]([id])
-- When referenced row is deleted, delete the row with the foreign key.
ON DELETE CASCADE
-- When referenced row is updated, do nothing.
ON UPDATE NO ACTION;
-- `UNIQUE`
ALTER TABLE [users]
ADD CONSTRAINT [uq_users_email]
UNIQUE ([email]);
-- `CHECK`
ALTER TABLE [orders]
ADD CONSTRAINT [ck_orders_quantity]
CHECK ([quantity] > 0 AND [quantity] < 100);
-- `DEFAULT`
ALTER TABLE [users]
ADD CONSTRAINT [df_users_created_at]
DEFAULT GETDATE() FOR [created_at];
-- `IDENTITY`: an auto-incr column, typically for generating primary keys.
ALTER TABLE [users]
ADD CONSTRAINT [df_users_id]
IDENTITY(1, 1);
Transactions
Transactions are used to ensure that a series of SQL statements are executed as a single unit. If any of the statements fail, the entire transaction is rolled back.
-- `BEGIN TRANSACTION` and `COMMIT TRANSACTION`
BEGIN TRANSACTION;
PRINT 'SQL statements...';
PRINT 'SQL statements...';
COMMIT TRANSACTION;
-- `ROLLBACK` to undo the transaction
BEGIN TRANSACTION;
PRINT 'SQL statements...';
PRINT 'Oops, something went wrong...';
ROLLBACK;
SQL Programming
Variables
-- Declare a variable
DECLARE @max_id int;
-- Set a variable's value
SET @max_id = 100;
SELECT @max_id = 100, @min_id = 1;
-- SQL variables can only hold one value.
-- If multiple is assigned at a time, the value of the last row is used.
SELECT @employee_name = [employee_name]
FROM [employees]
WHERE [employee_id] = 123;
Control Flow
Grouping
-- `BEGIN` & `END`
BEGIN
PRINT 'statement 1';
PRINT 'statement 2';
PRINT 'statement 3';
END
Branching
-- `IF ELSE` statement
IF EXISTS (
SELECT *
FROM [courses]
WHERE [name] = 'relational database'
)
BEGIN
DECLARE @avg_grade FLOAT;
SET @avg_grade = (
SELECT AVG([grade])
FROM [grades]
INNER JOIN [courses]
ON [grades].[crs_id] = [courses].[id]
WHERE [courses].[name] = 'relational database'
);
END
ELSE
PRINT "there is no course named relational database";
-- `CASE WHEN` expression
SELECT
[order_id],
[quantity],
CASE
WHEN [quantity] > 30 THEN 'The quantity is greater than 30'
WHEN [quantity] = 30 THEN 'The quantity is 30'
ELSE 'The quantity is under 30'
END AS [quantity_text]
FROM [order_details];
Looping
-- `WHILE` loop
DECLARE @counter INT;
SET @counter = 0;
WHILE @counter < 10
BEGIN
PRINT 'The counter is ' + CAST(@counter AS VARCHAR);
SET @counter = @counter + 1;
END
-- `BREAK` & `CONTINUE`
DECLARE @counter INT;
SET @counter = 0;
WHILE (1 = 1)
BEGIN
SET @counter = @counter + 1;
IF @counter > 10
BREAK;
IF @counter = 5
CONTINUE;
PRINT 'The counter is ' + CAST(@counter AS VARCHAR);
END
Stored Procedures
A procedure in SQL Server is a named set of SQL statements, stored in the database, that can be executed as a single unit. It can alter database tables, execute queries, and perform other operations.
-- Declaring a stored procedure
CREATE PROC [proc_customer_info]
-- `@customer_id` is the input parameter
@customer_id INT
AS -- start the procedure's code block
BEGIN
SELECT * FROM [customers]
WHERE customer_id = @customer_id
END
-- Executing a stored procedure
EXEC [proc_customer_info] 123;
-- Output parameters
CREATE PROC [proc_customer_info]
@customer_id INT,
-- `@customer_name` is the output parameter
@customer_name NVARCHAR(50) OUTPUT
AS
BEGIN
SELECT @customer_name = [customer_name]
FROM [customers]
WHERE [customer_id] = @customer_id
END
Functions
A function in SQL Server is a reusable piece of code that performs a specific task and returns a value. It must have at lease one input parameter and returns a value. It may not alter database objects.
-- Declaring a function
CREATE FUNCTION [fn_get_customer_name]
-- `@customer_id` is the input parameter
(@customer_id INT)
RETURNS NVARCHAR(50) -- the return type
AS
BEGIN
DECLARE @customer_name NVARCHAR(50);
SELECT @customer_name = [customer_name]
FROM [customers]
WHERE [customer_id] = @customer_id;
RETURN @customer_name;
END
-- Calling a function
SELECT [fn_get_customer_name](123) AS [customer_name];
-- Inline `RETURNS TABLE` function
CREATE FUNCTION [fn_get_orders]
(@customer_id INT)
RETURNS TABLE
AS
RETURN (
SELECT *
FROM [orders]
WHERE [customer_id] = @customer_id
);
-- Calling an inline `RETURNS TABLE` function
SELECT * FROM [fn_get_orders](123);
-- Multi-statement `RETURNS TABLE` function
CREATE FUNCTION [fn_get_orders]
(@customer_id INT)
RETURNS @orders TABLE (
[order_id] INT,
[order_date] DATETIME,
[total_amount] DECIMAL(10, 2)
)
AS
BEGIN
INSERT INTO @orders
SELECT [order_id], [order_date], [total_amount]
FROM [orders]
WHERE [customer_id] = @customer_id;
RETURN;
END
Triggers
Triggers are a special type of stored procedure that are automatically executed when certain events occur in the database.
-- `CREATE TRIGGER`
CREATE TRIGGER [trg_orders_insert]
ON [orders]
AFTER INSERT
AS
BEGIN
PRINT 'A new order has been inserted.';
END;
-- `INSTEAD OF`
CREATE TRIGGER [trg_orders_update]
ON [orders]
INSTEAD OF UPDATE
AS
BEGIN
PRINT 'An order has been updated.';
PRINT 'The update has been ignored.';
END;
-- `inserted` and `deleted` tables
CREATE TRIGGER [trg_orders_update]
ON [orders]
AFTER UPDATE
AS
BEGIN
DECLARE @order_id INT;
SELECT @order_id = [order_id] FROM [inserted];
PRINT 'An order has been updated.';
PRINT 'The order ID is ' + CAST(@order_id AS NVARCHAR(10));
END;
Cursors
A cursor in SQL Server is a database object used to retrieve and manipulate data row by row, allowing for sequential access to query results.
-- Declaring a cursor
DECLARE [customer_cursor] CURSOR FOR
SELECT [customer_name], [email]
FROM [customers]
WHERE [last_purchase_date] >= DATEADD(MONTH, -1, GETDATE());
-- Opening a cursor: before fetching rows, the cursor must be opened
OPEN [customer_cursor];
-- Fetching rows
DECLARE @customer_name NVARCHAR(50);
DECLARE @email NVARCHAR(50);
FETCH NEXT FROM [customer_cursor] INTO @customer_name, @email;
-- Closing a cursor & deallocating resources
CLOSE [customer_cursor];
DEALLOCATE [customer_cursor];
Security
Views
Views are virtual tables that are based on the result of a SELECT statement. They are used to simplify complex queries and to hide the complexity of the underlying tables.
-- `CREATE VIEW`
CREATE VIEW [v_users] AS
SELECT [id], [email], [created_at]
FROM [users]
WHERE [is_active] = 1;
-- `SELECT` from a view
SELECT * FROM [v_users];
-- Pass-through `UPDATE` & `DELETE`
-- (The view must be "simple" and directly "computed" from the underlying tables)
UPDATE [v_users]
SET [email] = 'sample@example.com'
WHERE [id] = 123;
-- `WITH CHECK OPTION`
CREATE VIEW [v_active_users] AS
SELECT [id], [email], [created_at]
FROM [users]
WHERE [is_active] = 1
-- this will only allow pass-though modifications that is visible in view
WITH CHECK OPTION;
Schema
A scheme in SQL Server refers is a way to organize database objects, such as tables, views, procedures, and functions, into logical groups.
-- Creating a schema
CREATE SCHEMA [security];
-- Creating a table in a schema
CREATE TABLE [security].[users] (
[id] INT,
[email] NVARCHAR(50),
[created_at] DATETIME
);
Logins, Users & Roles
-- `CREATE LOGIN`: allowing to connect to SQL Server.
CREATE LOGIN [sample_user] WITH PASSWORD = 'password';
-- `CREATE USER`:
-- an identity that can be associated with a login, and can be granted permissions.
CREATE USER [sample_user] FOR LOGIN [sample_user]; -- associate with a login
-- `CREATE ROLE`:
-- a group of users, and can be granted permissions.
CREATE ROLE [sample_role];
CREATE ROLE [sample_role] AUTHORIZATION [sample_user]; -- owned by a user
Permissions
-- `GRANT`: gives a permission to a user or role.
GRANT SELECT ON [users] TO [sample_role];
-- `REVOKE`: removes a previously granted permission.
REVOKE UPDATE ON [users] FROM [sample_role];
-- `DENY`: explicitly denies a permission to a user or role.
DENY INSERT ON [users] TO [sample_user];
-- `WITH GRANT OPTION`: allows the user to grant the permission to others.
GRANT SELECT ON [users] TO [sample_role] WITH GRANT OPTION;
SurrealDB
SurrealDB is document database that supports multiple
mental models, including document, graph, and table. It has a SQL-like query
language named SurrealQL
that enables advanced querying and data manipulation.
Features
- Multi-model database
- ACID compliant
Basics
CRUD
Create
-- record id field (`id`) is defined by default
DEFINE TABLE person SCHEMAFULL;
DEFINE FIELD name ON TABLE person TYPE string;
INSERT INTO person (name) VALUES ("John"), ("Jane");
INSERT INTO person { name: "John" };
INSERT INTO person [{ name: "John" }, { name: "Jane" }];
CREATE person SET name = "John";
DEFINE INDEX idx_name ON TABLE person COLUMNS name;
Read
SELECT * FROM person;
SELECT name
FROM person
WHERE name = "Jane";
SELECT count() AS person_count
FROM person
GROUP ALL;
Update
DEFINE FIELD last_name ON TABLE person TYPE string;
UPDATE person
SET last_name = "Doe"
WHERE name = "Jane";
REMOVE FIELD last_name ON TABLE person;
Delete
DELETE person WHERE name = "Jane";
REMOVE TABLE person;
Using Namespaces/Databases
USE NS test; -- Switch to the 'test' Namespace
USE DB test; -- Switch to the 'test' Database
-- Switch to the 'test' Namespace and 'test' Database
USE NS test DB test;
Data Relationships
This page is INCOMPLETE
This page is incomplete and is still being written.
In SurrealDB, data relationships can be model in three ways:
- embedded record: storing a document within another record field, sacrificing type safety and referential integrity.
- record link: storing a reference to another record, sacrificing referential integrity.
- graph relation:
-- embedded record
DEFINE TABLE person SCHEMAFULL;
DEFINE FIELD name ON TABLE person TYPE string;
DEFINE FIELD address ON TABLE person FLEXIBLE TYPE object; -- flexible typed document
INSERT INTO person:john {
name: "John",
address: { street: "123 Main St", city: "City" }
};
-- record link
DEFINE TABLE person SCHEMAFULL;
DEFINE FIELD name ON TABLE person TYPE string;
DEFINE FIELD address ON TABLE person TYPE record<address>; -- record link to 'address' table
DEFINE TABLE address SCHEMALESS;
INSERT INTO address:john_addr { street: "123 Main St", city: "City" };
INSERT INTO person:john {
name: "John",
address: address:john_addr
};
-- graph relation
Querying
This page is INCOMPLETE
This page is incomplete and is still being written.
WHERE
Condition
Refining Results
-- `ONLY`
Traversing Relations
-- doc notation: embedded record & link
SELECT
name,
address.city,
address.street
FROM person;
-- array notation: graph relation
Tmux
Tmux is a terminal multiplexer, enabling a number of terminals to be created, accessed, and controlled from a single screen.
Command Cheatsheet
tmux
: Creates a unnamed sessiontmux new -s <id>
: Create a named session
tmux a
: Attach to most recent sessiontmux a -t <id>
: Attach to specified session
tmux ls
: List all sessionstmux kill-session <id>
: Kill specified session
Keybind Cheatsheet
Ctrl + b: Prefix key.
- Session Management
- d: Detach
- Pane
- %: Split horizontal
- ": Split vertical
- direction arrow: Switch pane
- q: Quick jump to pane
- Ctrl + direction arrow: Resize
- Alt + direction arrow: Aggressive Resize
- Alt + number: Apply layout
- x: Kill pane
- ,: Rename pane
- Window
- c: Create new window
- w: Window List
- n: Next window
- &: Kill window
- $: Rename window
- Copy mode
- [: Enter copy mode
- (copy mode) space: starts copy
- (copy mode) enter: ends copy
Resources
NeoVim
NeoVim is a modernized version of Vim. It's a fork of Vim, with the goal of improving the codebase and adding new features.
Cheatsheet
- substitute:
:s/old/new/
: substitute the first occurrence ofold
withnew
in the current line.:s/old/new/g
: substitute all occurrences ofold
withnew
in the current line.:#,#s/old/new/g
: substitute all occurrences ofold
withnew
in the range of lines.:%s/old/new/g
: substitute all occurrences ofold
withnew
in the entire file.
Plugins
- lazy.nvim: A modern plugin manager for Neovim.
- telescope.nvim: A highly extendable fuzzy finder.
- neo-tree.nvim: Easily manage the file system and other tree like structures.
- trouble.nvim: A pretty list for showing diagnostics, references, telescope results, quickfix and location lists.
- undotree.vim: The undo history visualizer for Vim.
- todo-comments.nvim: Highlight, list and search todo comments in your projects.
Resources
- dotfyle: Discover and share Neovim plugins.
- kickstart.nvim: A launch point for your personal nvim configuration
- LazyVim: A flexible, battery included Neovim distribution.
Git
Git is a distributed version control system. It is a tool to manage source code history. It is designed to handle small to very large projects with speed and efficiency.
Basics
Git Repository
Git repository is a collection of files and folders that are tracked by Git. It
is a directory that contains the .git
directory. The .git
directory is where
Git stores the metadata and object database for the project.
# Create a new repository
git init
A newly created repository looks like this:
.
└── .git
├── branches
├── config
├── description
├── HEAD
├── hooks
│ ├── applypatch-msg.sample
│ ├── commit-msg.sample
│ ├── fsmonitor-watchman.sample
│ ├── post-update.sample
│ ├── pre-applypatch.sample
│ ├── pre-commit.sample
│ ├── pre-merge-commit.sample
│ ├── prepare-commit-msg.sample
│ ├── pre-push.sample
│ ├── pre-rebase.sample
│ ├── pre-receive.sample
│ ├── push-to-checkout.sample
│ └── update.sample
├── info
│ └── exclude
├── objects
│ ├── info
│ └── pack
└── refs
├── heads
└── tags
Recording Changes
Each file in the working directory can be tracked
or untracked
, and tracked
files can be unmodified
, modified
, or staged
. The staged files can be
committed
to the repository.
# Check the status of the working directory
git status
# Add a file to the staging area
git add <file>
# Commit the staged changes
git commit -m "commit message"
# Show the changes between the working directory and the staging area
git diff
# View the commit history
git log
It is possible to tell git to ignore files by creating a .gitignore
file. Its
syntax is simple: each line contains a pattern for files to ignore.
# https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository
# ignore a file named `ignore-me.txt`
ignore-me.txt
# ignore all `.a` files
*.a
# but do track `lib.a`, even though you're ignoring `.a` files above
!lib.a
# only ignore the `TODO` file in the current directory, not `sub-dir/TODO`
/TODO
# ignore all files in any directory named `build/`
build/
# ignore `doc/notes.txt`, but not `doc/server/arch.txt`
doc/*.txt
# ignore all `.pdf` files in the `doc/` directory and any of its subdirectories
doc/**/*.pdf
We can also explicitly remove a file from the staging area or tracked files, or discard changes in the working directory.
# Restore a file from the staged area
# https://stackoverflow.com/a/65434709
git restore --staged <file>
git rm --cached
# Remove files from the working tree (tracked files)
git rm <file>
# Discard changes in the working directory
git restore <file>
# Move or rename a file
git mv <old-file> <new-file>
Tagging
Git can tag a specific point in a repo' history as being important.
# List existing tags
git tag
# List tags that matches "v1.8.5*"
git tag -l "v1.8.5*"
# Create a annotated tag v1.0 with message "First stable release"
git tag -a v1.0 -m "First stable release"
# Create a lightweight tag v1.4
git tag v1.4
# Tagging previous commit
git tag -a v1.2 9fceb02
# Display tag information
git show v1.4
# Deleting tags
git tag -d v1.2
# Pushing all tags to remote
git push origin --tags
# Delete a remote tag
git push origin --delete v1.2
CMake
CMake is a cross-platform free and open-source software tool for managing the build process of software using a compiler-independent method.
Basics
Directory Structure
CMake is a build system generator. It generates build files in the specified
directory, usually build/
, from the CMakeLists.txt
file in the project
directory (top-level directory).
project_dir/
├── build/
│ ├── CMakeCache.txt
│ ├── CMakeFiles
│ │ └── ...
│ ├── cmake_install.cmake
│ └── Makefile
├── ...
└── CMakeLists.txt
Build & Install Binary
cmake_minimum_required(VERSION 3.22.1)
project(actual_project_name)
# Build executable
add_executable(executable_name main.cpp)
# Install binary to PATH
install(TARGETS executable_name DESTINATION bin)
Build & Install Library
cmake_minimum_required(VERSION 3.22.1)
project(actual_project_name)
# Build library
add_library(library_name lib.cpp)
# Install library to INCLUDE
install(TARGETS library_name DESTINATION lib)
Linking Library
# Linking system installed library
target_link_libraries(target_name library_name)
# Linking library by path
target_link_directories(target_name PRIVATE path/to/the/library/)
target_link_libraries(target_name library_name)
Resources
.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
# Quality of life
set -g mouse on
setw -g mode-keys vi
# 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
# 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
########## 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"
# Status bar basic setting
set -g status-interval 1
set -g status-left-length 20
set -g status-right-length 50
# [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=\#5e5faf]◢\
#[bg=\#5e5faf, fg=colour189]#I\
#[bg=colour189, fg=\#5e5faf]◤\
#[bg=colour189, fg=\#5e5faf, bold]#W\
#[bg=colour0, fg=colour189]◤"
# [win/cur/{tab}/----------/time]
set-window-option -g window-status-format "\
#[bg=colour0, fg=#5e5faf]◢\
#[bg=#5e5faf, fg=colour189]#I\
#[bg=\#4e4e4e, fg=#5e5faf]◤\
#[bg=\#4e4e4e, fg=colour189]#W\
#[bg=colour0, fg=\#4e4e4e]◤"
# [win/cur/tab/----------/{time}]
set-option -g status-right "\
#[bg=colour0, fg=\#5e5faf]◢\
#[bg=\#5e5faf, fg=colour189]%d %b %Y %H:%M "
# Window list color
set-window-option -g mode-style bg=#5e5faf,fg=colour189