Rust Language Cheat Sheet - https://cheats.rs/
Easy Rust - https://dighomon.github.io/easy_rust/
A Gentle Introduction Rust - https://stevedonovan.github.io/rust-gentle-intro/
Installation - https://doc.rust-lang.org/book/ch01-01-installation.html
The standard library reference - https://doc.rust-lang.org/std/
The rustc book - https://doc.rust-lang.org/stable/rustc/
Cookin' with Rust - https://rust-lang-nursery.github.io/rust-cookbook/
The Embedded Rust Book - https://docs.rust-embedded.org/book/intro/index.html
Learn Rust with Entirely too many Linked Lists -
https://rust-unofficial.github.io/too-many-lists/
The Rust community's crate registry - https://crates.io/
Are we learning yet? - https://www.arewelearningyet.com/
Open-Source Rust crates for numerical simulation - https://www.dimforge.com/
Module ndarray for NumPy users -
https://docs.rs/ndarray/latest/ndarray/doc/ndarray_for_numpy_users/index.html
From Python into Rust -
https://github.com/rochachrumo/py2rs#getting-started-with-rust
Rust quick reference - https://quickref.me/rust
Programming Idioms with Go and Rust -
https://programming-idioms.org/cheatsheet/Go/Rust
Getting started with Programming in Rust -
https://jesselawson.github.io/getting-started-with-rust/

# to download Rustup and install Rust
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Rust updates very frequently, if you have installed Rustup some time ago, chances
are your Rust version is out of date. Get the latest version of Rust by running
$ rustup update

# version
$ rustc --version

# keywords
The following keywords currently in use and they cannot be used as identifiers.
   abstract   as       async   await    become   box        break     const
   continue   crate    do      dyn      else     enum       extern    false
   final      fn       for     if       impl     in         let       loop
   macro      match    mod     move     mut      override   priv      pub
   ref        return   Self    self     static   struct     super     trait
   true       try      type    typeof   union    unsafe     unsized   use
   virtual    where    while   yield

Rust By Example
$ git clone https://github.com/rust-lang/rust-by-example.git
$ cd rust-by-example
$ cargo install mdbook
$ mdbook build
$ mdbook serve

# list of supported targets
$ rustc --print target-list

# Rust's package manager
$ cargo --help

# list of all avaliable commands
$ cargo --list

# list installed component
$ rustup show

# install component
$ rustup component add clippy

# create new project using cargo
$ cargo new --lib <project>     // create new package directory

# create a new Cargo package in an existing directory
$ cargo init           // without args creates files in current location
$ cargo init --bin     // create a package with a binary target (src/main.rs)
$ cargo init --lib     // create a package with a library target (src/lib.rs)
$ cargo init <project> // with <project name> as args acts like cargo new
Ref: https://doc.rust-lang.org/cargo/commands/cargo-init.html

# working on an existing Cargo package
If you download an existing package that uses Cargo, it's really easy to get
going. First, get the package, in this example, we'll use regex cloned from its
repository on GitHub:
$ git clone https://github.com/rust-lang/regex.git
$ cd regex
To build, use cargo build:
$ cargo build
    Compiling regex v1.5.0 (file://path/to/package/regex)
This will fetch all of the dependencies and then build them, along with the
package.
Ref: https://doc.rust-lang.org/cargo/guide/working-on-an-existing-project.html

# rust formatting
The rustfmt tool reformats your code according to the Rust community code style.
$ rustfmt program.rs
$ rustfmt lib.rs program.rs

# fix compiler warnings
The rustfix can automatically fix compiler warnings that have a clear way to
correct the problem that's likely what you want.
$ rustfix program.rs

# rust lint
The clippy tool is a collection of lints to analyze your code so you can catch
common mistakes and improve your Rust code.
$ cargo clippy

# rust compile
$ rustc program.rs
$ rustc -O program.rs       // optimized compile

# adding dependencies using Cargo
Cargo allow to add dependencies that your program needs to run. Adding a
dependency is extremely easy with Cargo. Every Rust package includes a
Cargo.toml file, which contains a list (empty by default) of dependencies. Open
the file, find the [dependencies] section, and add the library you want to
include in your package. For example, to add the rand library as your
dependency.

[dependencies]
rand = "0.3.14"

Try building your package to see what happens.
$ cargo build

New versions of libraries keep coming, and Cargo provides an easy way to update
all of their dependencies using the update command:
$ cargo update

To update specific libraries using the -p flag followed by the package name:
$ cargo update -p rand

# create documnetation
//! # it uses markdown notation
//! - bullet
//! - points

# primitive types
# integers
signed integers: i8, i16, i32, i64, i128, isize
unsigned integers: u8, u16, u32, u64, u128, usize
isize and usize: number of bits on the machine (architecture)

# science in rust lang
ndarray - https://docs.rs/ndarray/latest/ndarray/index.html
nalgebra - https://docs.rs/nalgebra/latest/nalgebra/index.html
plotters - https://plotters-rs.github.io/home/#!/

# don't array but vector
don't pass a variable to a funtion and create an array of that variable size,
instead pass a variable to a function and create a vector ot that variable size.
Example:
Not work:
fn fibonacci(n: usize) -> usize {
    let mut f = [0; n]; // create an array of size n and assign 0 (int type)
    f[0] = 1; f[1] = 1; // the above line fails since size of n should be a
                        // constant not a variable for an array creation
    for i in 1..n - 1 {
        f[i + 1] = f[i] + f[i - 1];
    }

    return f[n - 1];
}

Work well:
fn fibonacci(n: usize) -> usize {
    let mut f = vec![0; n]; // create a vector of size n and assign 0 (int type)
    f[0] = 1; f[1] = 1;

    for i in 1..n - 1 {
        f[i + 1] = f[i] + f[i - 1];
    }

    return f[n - 1];
}

# comparison between Python and Rust
Python                                          Rust
- interpreted                                   - compiled
- strongly, dynamically typed                   - strongly and statically typed
- garbage collected                             - static memory management
- structured, object-oriented, functional       - structured, object-oriented, functional, concurrent
- Exceptions                                    - no exceptions
- duck-typing, OO inheritance                   - no inheritance, uses traits
- v3.0 stable since 2008                        - stable since 2015

00. Hello world
#!/usr/bin/env python
"classic Hello World example in Python"         //! classic Hello World example in Rust

if __name__ == '__main__':                      fn main() {
    print("Hello World!")                           println!("Hello World!");
                                                }


//! - module documentation string
println! - a macro in rust (a function in python)

# ecosystem
                                Python                              Rust
linter                          pylint, pep8                        cargo clippy
language server protocal        python-language-server              RLS, rust-analyzer
code formatting                 black, yapf, autopep8               cargo fmt
build binary                    setuptools, py2exe                  cargo build
test                            unittest, pytest                    cargo test
build environment               virtualenv, pip, requirements.txt   cargo new, cargo update, Cargo.toml
documentation                   Sphinx based, doxygen               cargo doc
benchmark                       cProfile                            cargo bench, criterion.rs


# factorial
fn factorial(n: usize) -> usize {
    (1..n+1).product()
}

fn main() {
    const N: usize = 12;
    println!("{}", factorial(N));
}


# prime
fn prime(number: i32) -> String {
    /* function to check prime */
    let sqrt_number = f64::sqrt(number.into());
    for i in 2..(sqrt_number as i32) + 1 {
        if number % i == 0 {
            return format!("{} is not a prime number", number);
        }
    }
    return format!("{} is a prime number", number);
}

fn main() {
    for n in 4..30 {
        println!("{}", prime(n));
    }
}


# vectors
A vector Vec<T> is a resizable array of elements of type T, allocated on the
heap. There are several ways to create vectors. The simplest is to use the vec!
macro, which gives us a syntax for vectors that looks very much like an array
literal:
let mut primes = vec![2, 3, 5, 7]; // i32 type by default

The vec! macro is equivalent to calling Vec::new to create a new, empty vector
and then pushing the elements onto it, which is another idiom:
let mut pal = Vec::new();
pal.push("step");
pal.push("on");
pal.push("no");
pal.push("pets");
assert_eq!(pal, vec!["step", "on", "no", "pets"]);

Another possibility is to build a vector from the values produced by an
iterator:
let v: Vec<i32> = (0..5).collect();
assert_eq!(v, [0, 1, 2, 3, 4]);

You'll often need to supply the type when using collect (as we've done here),
because it can build many different sorts of collections, not just vectors. By
specifying the type of v, we've made it unambiguous which sort of collection we
want.

The vector's len method returns the number of elements it contains now, its
capacity method returns the number of elements it could hold without
reallocation:
let mut v = Vec::with_capacity(2);
assert_eq!(v.len(), 0);
assert_eq!(v.capacity(), 2);

v.push(1);
v.push(2);
assert_eq!(v.len(), 2);
assert_eq!(v.capacity(), 2);

v.push(3);
assert_eq!(v.len(), 3);
println!("capacity is now {}", v.capacity()); // capacity is now 3

You can insert and remove elements wherever you like in a vector, although these
operations shift all the elements after the affected position forward or
backward, so they may be slow if the vector is long:
let mut v = vec![10, 20, 30, 40, 50];

// make the element at index 3 be 35
v.insert(3, 35);
assert_eq!(v, [10, 20, 30, 35, 40, 50]);

// remove the element at index 1
v.remove(1);
assert_eq!(v, [10, 30, 35, 40, 50]);

You can use the pop method to remove the last element and return it. More
precisely, popping a value from a Vec<T> returns an Option<T>: None if the
vector was already empty, or Some(v) if its last element had been v:
let mut v = vec!["Snow Puff", "Glass Gem"];
assert_eq!(v.pop(), Some("Glass Gem"));
assert_eq!(v.pop(), Some("Snow Puff"));
assert_eq!(v.pop(), None);

# adding all elements in a container
let mut primes = vec![2, 3, 5, 7];
assert_eq!(primes.iter().sum::<i32>(), 17);
primes.push(11);
primes.push(13);
assert_eq!(primes.iter().sum::<i32>(), 41);

# multiply all elements in a container
let mut primes = vec![2, 3, 5, 7];
assert_eq!(primes.iter().product::<i32>(), 210);
primes.push(11);
primes.push(13);
assert_eq!(primes.iter().product::<i32>(), 30030);

# floating-point types
The types f32 and f64 have associated constants for the IEEE-required special
values like INFINITY, NEG_INFINITY (negative infinity), NAN (the not-a-number
value), and MIN and MAX (the largest and smallest finite values):
assert!((-1. / f32::INFINITY).is_sign_negative());
assert_eq!(-f32::MIN, f32::MAX);

The f32 and f64 types provide a full complement of methods for mathematical
calculations; for example, 2f64.sqrt() is the double-precision square root of
two. Some examples:
assert_eq!(5f32.sqrt() * 5f32.sqrt(), 5); // exactly 5.0, per IEEEE
assert_eq!((-1.01f64).floor(), -2.0);

As with integers, you usually won't need to write out type suffixes on
floating-point literals in real code, but when you do, putting a type on either
the literal or the function will suffice.
println!("{}", (2.0_f64).sqrt());
println!("{}", f64::sqrt(2.0));

# math constants
std::f64::consts::PI

# Expressions
Expression type         Example                         Related traits
Addition                n + 1                           Add
Subtraction             n - 1                           Sub
Multiplication          n * 2                           Mul
Division                n / 2                           Div
Remainder (modulus)     n % 2                           Rem
Less than               n < 1                           std::cmp::PartialOrd
Less than or equal      n <= 1                          std::cmp::PartialOrd
Greater than            n > 1                           std::cmp::PartialOrd
Greater than or equal   n >= 1                          std::cmp::PartialOrd
Equal                   n == 1                          std::cmp::PartialEq
Not equal               n != 1                          std::cmp::PartialEq
Assignment              x = val
Compound assignment     x += 1                          AddAssign
                        x -= 1                          SubAssign
                        x *= 1                          MulAssign
                        x /= 1                          DivAssign
                        x %= 1                          RemAssign
                        x <<= 1                         ShlAssign
                        x >>= 1                         ShrAssign
                        x &= 1                          BitAndAssign
                        x ^= 1                          BitXorAssign
                        x != 1                          BitOrAssign


# ndarray - numpy equivalents
import numpy as np              # import numpy library in python
use ndarray::prelude::*;        / use the ndarray library in rust

Array creation:

np.array([[1.,2.,3.], [4.,5.,6.]])  # 2x3 floating-point array literal
array![[1.,2.,3.], [4.,5.,6.]]      / 2x3 floating-point array literal
arr2(& [[1.,2.,3.], [4.,5.,6.]])    / 2x3 floating-point array literal

np.arange(0., 10., 0.5)             # create a 1-D array with values 0., 0.5, ..., 9.5
np.r_[:10.:0.5]                     # create a 1-D array with values 0., 0.5, ..., 9.5
Array::range(0., 10., 0.5)          / create a 1-D array with values 0., 0.5, ..., 9.5

np.linspace(0., 10., 11)            # create a 1-D array with 11 elements with values 0., ..., 10.
np.r_[:10.:11]                      # create a 1-D array with 11 elements with values 0., ..., 10.
Array::linspace(0., 10., 11)        / create a 1-D array with 11 elements with values 0., ..., 10.

np.logspace(2.0, 3.0, num=4, base=10.0) # create a 1-D array with 4 elements with values 100., 215.4, 464.1, 1000.
Array::logspace(10.0, 2.0, 3.0, 4)      / create a 1-D array with 4 elements with values 100., 215.4, 464.1, 1000.

np.geomspace(1., 1000., num=4)      # create a 1-D array with 4 elements with values 1., 10., 100., 1000.
Array::geomspace(1e0, 1e3, 4)       / create a 1-D array with 4 elements with values 1., 10., 100., 1000.

np.ones((3, 4, 5))                  # create a 3x4x5 array filled with ones (inferring the element type)
Array::ones((3, 4, 5))              / create a 3x4x5 array filled with ones (inferring the element type)

np.zeros((3, 4, 5))                 # create a 3x4x5 array filled with zeros (inferring the element type)
Array::zeros((3, 4, 5))             / create a 3x4x5 array filled with zeros (inferring the element type)

np.zeros((3, 4, 5), order='F')      # create a 3x4x5 array with Fortran (column-major) memory layout filled with zeros (inferring the element type)
Array::zeros((3, 4, 5).f())         / create a 3x4x5 array with Fortran (column-major) memory layout filled with zeros (inferring the element type)

np.zeros_like(a, order='C')         # create an array of zeros of the shape as a, with row-major
Array::zeros(a.raw_dim())           # memory layout (unlike NumPy, this infers the element type from
                                    # context instead of duplicating a's element type)

np.full((3, 4), 7.)                 # create a 3x4 array filled with the value 7.
Array::from_elem((3, 4), 7.)        / create a 3x4 array filled with the value 7.

np.eye(3)                           # create a 3x3 identity matrix (inferring the element type)
Array::eye(3)                       / create a 3x3 identity matrix (inferring the element type)

np.diag(np.array([1, 2, 3]))            # create a 3x3 matrix with [1, 2, 3] as diagonal and zeros elsewhere (inferring the element type)
Array2::from_diag(&arr1(&[1, 2, 3]))    / create a 3x3 matrix with [1, 2, 3] as diagonal and zeros elsewhere (inferring the element type)

np.array([1, 2, 3, 4]).reshape((2, 2))              # create a 2x2 array from the elements in the list/Vec
Array::from_shape_vec((2, 2), vec![1, 2, 3, 4])?    / create a 2x2 array from the elements in the list/Vec

np.array([1, 2, 3, 4]).reshape((2, 2), order='F')       # create a 2x2 array from the elements in the list/Vec using Fortran (column-major) order
Array::from_shape_vec((2, 2).f(), vec![1, 2, 3, 4])?    / create a 2x2 array from the elements in the list/Vec using Fortran (column-major) order

np.random                   # create arrays of random numbers

Indexing and slicing
a[-1]                       # access the last element in 1-D array a
a[a.len() - 1]              / access the last element in 1-D array a

a[1, 4]                     # access the element in row 1, column 4
a[[1, 4]]                   / access the element in row 1, column 4

a[1]                        # get a 2-D subview of a 3-D array at index 1 of axis 0
a[1, :, :]                  # get a 2-D subview of a 3-D array at index 1 of axis 0
a.slice(s![1, .., ..])      / get a 2-D subview of a 3-D array at index 1 of axis 0
a.index_axis(Axis(0), 1)    / get a 2-D subview of a 3-D array at index 1 of axis 0

a[:5]                                       # get the first 5 rows of a 2-D array
a[0:5]                                      # get the first 5 rows of a 2-D array
a[0:5, :]                                   # get the first 5 rows of a 2-D array
a.slice(s![..5, ..]                         / get the first 5 rows of a 2-D array
a.slice(s![0..5, ..])                       / get the first 5 rows of a 2-D array
a.slice_axis(Axis(0), Slice::from(0..5))    / get the first 5 rows of a 2-D array

a[-5:]                                      # get the last 5 rows of a 2-D array
a[-5:, :]                                   # get the last 5 rows of a 2-D array
a.slice(s![-5.., ..])                       / get the last 5 rows of a 2-D array
a.slice_axis(Axis(0), Slice::from(-5..))    / get the last 5 rows of a 2-D array

a[:3, 4:9]                  # columns 4, 5, 6, 7, and 8 of the first 3 rows
a.slice(s![..3, 4..9])      / columns 4, 5, 6, 7, and 8 of the first 3 rows

a[1:4:2, ::-1]              # rows 1 and 3 with the columns in reverse onder
a.slice(s![1..4;2, ..;-1]   / rows 1 and 3 with the columns in reverse onder

Shape and strides
np.ndim(a)              # get the number of dimensions of array a
a.ndim                  # get the number of dimensions of array a
a.ndim()                / get the number of dimensions of array a

np.size(a)              # get the number of elements in array a
a.size                  # get the number of elements in array a
a.len()                 / get the number of elements in array a

np.shape(a)             # get the shape of array a
a.shape                 # get the shape of array a
a.shape()               / get the shape of array a
a.dim()                 / get the shape of array a

a.shape[axis]           # get the length of an axis
a.len_of(Axis(axis))    / get the length of an axis

a.strides               # get the strides of array a
a.strides()             / get the strides of array a

np.size(a) == 0         # check if the array has zero elements
a.size == 0             # check if the array has zero elements
a.is_empty()            / check if the array has zero elements

Mathematics
a.transpose()           # transpose of array a
a.T                     # transpose of array a
a.t()                   / transpose of array a
a.reversed_axes()       / transpose of array a

mat1.dot(mat2)          # 2-D matrix multiply
mat1.dot(&mat2)         / 2-D matrix multiply

mat.dot(vec)            # 2-D matrix dot 1-D column vector
mat.dot(&vec)           / 2-D matrix dot 1-D column vector

vec.dot(mat)            # 1-D row vector dot 2-D matrix
vec.dot(&mat)           / 1-D row vector dot 2-D matrix

vec1.dot(vec2)          # vector dot product
vec1.dot(&vec2)         / vector dot product

a * b, a + b, etc.      # element-wise arithmetic operations
a * b, a + b, etc.      / element-wise arithmetic operations

a**3                    # element-wise power of 3
a.mapv(|a| a.powi(3))   / element-wise power of 3

np.sqrt(a)              # element-wise square root for f64 array
a.mapv(f64::sqrt)       / element-wise square root for f64 array

(a > 0.5)               # array of bools of same shape as a with true where a > 0.5 and false elsewhere
a.mapv(|a| a > 0.5)     / array of bools of same shape as a with true where a > 0.5 and false elsewhere

np.sum(a)               # sum the elements in a
a.sum()                 # sum the elements in a
a.sum()                 / sum the elements in a

np.sum(a, axis=2)       # sum the elements in a along azis 2
a.sum(axis=2)           # sum the elements in a along azis 2
a.sum_axis(Axis(2))     / sum the elements in a along azis 2

np.mean(a)          # calculate the mean of the elements in f64 array a
a.mean()            # calculate the mean of the elements in f64 array a
a.mean().unwrap()   / calculate the mean of the elements in f64 array a

np.mean(a, axis=2)      # calculate the mean of the elements in a along axis 2
a.mean(axis=2)          # calculate the mean of the elements in a along axis 2
a.mean_axis(Axis(2))    / calculate the mean of the elements in a along axis 2

np.allclose(a, b, atol=1e-8)    # check if the arrays' elementwise differences
                                 are within an absolute tolerance (it requires
                                 the approx feature-flag)
a.abs_diff_eq(&b, 1e-8)         / check if the arrays' elementwise differences
                                 are within an absolute tolerance (it requires
                                 the approx feature-flag)

np.diag(a)      # view the diagonal of a
a.diag()        / view the diagonal of a

np.linalg       # linear algebra (matrix inverse, solving decompositions, etc.)


Array manipulation
a[:] = 3.       # set all array elements to the same scalar value
a.fill(3.)      / set all array elements to the same scalar value

a[:] = b        # copy the data from array b into array a
a.assign(&b)    / copy the data from array b into array a

np.concatenate((a,b), axis=1)                # concatenate arrays a and b along axis 1
concatenate![Axis(1), a, b]                  # concatenate arrays a and b along axis 1
concatenate(Axis(1), &[a.view(), b.view()])  / concatenate arrays a and b along axis 1

np.stack((a,b), axis=1)                      # stack arrays a and b along axis 1
stack![Axis(1), a, b]                        / stack arrays a and b along axis 1
stack(Axis(1), vec![a.view(), b.view()])     / stack arrays a and b along axis 1

a[:,np.newaxis]                 # create an view of 1-D array a, inserting a new axis 1
np.expand_dims(a, axis=1)       # create an view of 1-D array a, inserting a new axis 1
a.slice(s![.., NewAxis])        / create an view of 1-D array a, inserting a new axis 1
a.insert_axis(Axis(1))          / create an view of 1-D array a, inserting a new axis 1

a.transpose()           # transpose of array a
a.T                     # transpose of array a
a.t()                   / transpose of array a
a.reversed_axes()       / transpose of array a

np.diag(a)              # view the diagonal of a
a.diag()                / view the diagonal of a

a.flatten()                             # create a 1-D array by flattening a
use std::iter::FromIterator;
Array::from_iter(a.iter().cloned())     / create a 1-D array by flattening a

Iteration
a.flat                  # iterator over the array elements in logical order
a.iter()                / iterator over the array elements in logical order

np.ndenumerate(a)       # flat iterator yielding the index along with each element reference
a.indexed_iter()        / flat iterator yielding the index along with each element reference

iter(a)                 # iterator over the first (outermost) axis, yielding each subview
a.outer_iter()          / iterator over the first (outermost) axis, yielding each subview
a.axis_iter(Axis(0))    / iterator over the first (outermost) axis, yielding each subview

Type conversions
a.astype(np.float32)        / convert u8 array infallibly to f32 array with
a.mapv(|x| f32::from(x))    / std::convert::From, generally recommended

a.astype(np.int32)          / upcast u8 array to i32 array wit std::convert::From, preferable
a.mapv(|x| i32::from(x))    / over as because it ensures at compile-time that the conversion is lossless

a.astype(np.uint8)                      / try to convert i8 array to u8 array, panic if any value cannot
a.mapv(|x| u8::try_from(x).unwrap())    / be converted lossless at runtime (e.g. negative value)

a.astype(np.int32)      / convert f32 array to i32 array with "saturating" conversion; care needed because
a.mapv(|x| x as i32)    / it can be a lossy conversion or result in non-finite values!

Convenience methods for 2-D arrays
len(a) or a.shape[0]            # get the number of rows in a 2-D array
a.nrows()                       / get the number of rows in a 2-D array

a.shape[1]                      # get the number of columns in a 2-D array
a.ncols()                       / get the number of columns in a 2-D array

a[1] or a[1,:]                  # view (or mutable view) of row 1 in a 2-D array
a.row(1) or a.row_mut(1)        / view (or mutable view) of row 1 in a 2-D array

a[:,4]                          # view (or mutable view) of columns 4 in a 2-D array
a.column(4) or a.column_mut(4)  / view (or mutable view) of columns 4 in a 2-D array

a.shape[0] == a.shape[1]        # check if the array is square
a.is_square()                   / check if the array is square

Reference: https://docs.rs/ndarray/latest/ndarray/doc/ndarray_for_numpy_users/index.html
