Skip to content

Instantly share code, notes, and snippets.

@VadimBrodsky
Last active February 14, 2019 03:24
Show Gist options
  • Save VadimBrodsky/365e1c652fbe62796870f5d463c120fc to your computer and use it in GitHub Desktop.
Save VadimBrodsky/365e1c652fbe62796870f5d463c120fc to your computer and use it in GitHub Desktop.
Rust Lang
*
!/**/
!*.*
*.exe
*.pdb

Rust Lang

Resources

Tools

  • Compile the source using rustc filename.rs
  • Compile with Cargo cargo build in debug mode
  • Compile with Cargo cargo build --release in release mode
  • Run with cargo cargo run
  • Generate application project structure cargo new hello_world --bin
  • Compiler check without emitting a binary cargo check
  • Update a dependency cargo update
  • Crate docs cargo doc --open
  • Format Rust code with rustfmt filename.rs
  • Browser documentation with rustup doc --std

Rust Style

  • Snake case for filenames
  • Indent 4 spaces

Typical folder structure:

Cargo.toml
Cargo.lock
/src
  main.rs
  lib.rs
/target
  debug/
  release/

Identifiers

  • The first character is a letter, the remaining characters are alphanumeric or _
  • The first character is _, more than one character, remaining characters are alphanumeric or _
  • Raw identifiers start with r#
let r#fn = "this var is named fn";

// call a function named match
r#match();

Variables and Mutability

  • Variable scope is created with {}
  • Rust allows to redeclare variables with the same names, encouraging variable shadowing especially for type conversions
  • Variable bindings are immutable by default
  • Set binding to mutable with mut
  • Constants can be defined with const, no fixed address
  • Static variables can be declared with static, cannot be mut unless used in an unsafe block
  • Type definitions stated after the variable name let bigint: i64 = 0;

Primitive Data Types

  • Rust can infer the data type of a variable
  • Number data types
    • i8 u8 i16 u16 i32 u32 i64 u64
  • Float data types
    • f32 f64
  • Text data types
    • char

Operators

  • Standard arithmetic order of precedence
  • Rust does not have -- or ++
  • Take care when working with different number types
  • Bitwise and Shifts work only on integers

Expressions

  • Rust uses C-Style operators
  • There are no brackets around conditionals
  • Must include curly brackets around blocks
  • Nearly everything has a value and can be used as an expression

Functions

  • Rust requires explicit type definitions for functions
  • The return statement can be omitted in Rust
  • The body of a function {} has the value of its last expression
  • Values can be passed by reference, a reference is created by & and dereferenced by *
  • Mutable references allow to modify function parameters &mut and dereferenced by *
  • Void return value type is denoted by ()

Namespaces

  • To import code into the namespace use the use statement
  • Fully qualified names are delimited by ::
  • The Rust prelude implicitly brings code into the working namespace

Arrays and Slices

  • Arrays are indexed from zero and fixed in size
  • The type of the array includes its size
  • Slices are views into an underlying array of values
  • Rust slices keep track of their size
  • Have to explicitly say that you want to pass an array as a slice using the & operator
  • Cannot print an array with {}, but can do a debug print with {:?}
  • Array contains values of only one type, values are arranged next to each other in memory for efficient access
  • Slices never make a copy of the data, they borrow their data from arrays
  • Rust knows the size of an array at compile-time
  • Size of a slice is only known at run-time

Optional Values

  • Rust has a Some and a None types
  • These types offer helper methods:
    • .is_some
    • .is_none
    • .unwrap
  • unwrapping a none type would cause a panic, but it can be done safely
  • Rust offers a shortcut to .unwrap_or for safe unwrapping with a fallback
  • Panics in Rust are memory safe, they happen before any illegal access to memory

Vectors

  • Vectors are mutable and resizable arrays
  • Behave like slices
  • Declared via a constructor let mut v = Vec::new()
  • When a vector is modified or created, it allocates from the heap and becomes the owner of that memory.
  • The slice borrows the memory from the vector
  • When the vector dies or drops, it lets the memory go
  • Vectors have size and capacity
  • vec! macro is useful to initialize a vector
  • Vector methods:
    • .push - add item to the end
    • .pop to drop the last items
    • .extend add items using any compatible iterator
    • .insert to insert values into a vector at arbitrary positions
    • .remove to remove, but it is less efficient than pushing and popping
    • .clear the size becomes zero, but retain the old capacity
    • .sort to sort
    • .dedup to remove duplicates
    • .clone to clone
  • Vectors compare with each other and with slices by value

Iterators

  • An iterator is an object with next method which returns an Option.
  • As long as that value is not None, will keep calling next.
  • Use .iter() method to convert an Array to an iterator
  • Slices are implicitly converted to iterators
  • .windows method returns an iterator of slices, overlapping windows of values
  • .chuncks method returns an iterator of slices, not overlapping

Strings

  • Strings in Rust are like Vectors and Slices
    • Literal "strings" is a static string slice
    • Instance String::new() or .to_string is a dynamic allocated string
  • The borrow operator & can coerce String into &str, just as Vec<T> could be coerced into &[T]
  • Under the hood String is a Vec<u8> and &str is &[u8] with the bytes representing valid UTF-8 text
  • Like a vector, can push and pop characters
  • .push_str or += to concatenate strings
  • If a type can be displays with {} it can be converted to a string with .to_string
  • The format! macro is useful to build up complicated strings, like println!
  • Cannot index strings, because in UTF-8 a character may be a number of bytes
  • The Rust char type is a 4-byte Unicode code point
  • Strings are not arrays of chars!
  • String slicing may explode like vector indexing, because it uses byte offsets
  • Slice strings only using valid offsets come from string methods

Command Line Arguments

  • std::env:args to access command-line arguments, returns an iterator over the arguments as strings

Matching

  • match consists of several patterns with a matching value followed the fat arrow, separated by commas
  • It unwraps the value from Option
  • Must specify all possibilities including None
  • match can operate like a C switch statement
  • _ is a fall-back case, must be specified
  • match can operate with ranges as well
let text = match n {
    0 => "zero",
    1 => "one",
    2 => "two",
    _ => "manu",
};
let text = match n {
    0...3 => "small",
    4...6 => "medium",
    _ => "large",
};
docker run -it --rm -e USER=$USER -v "${PWD}:/usr/src/" -w /usr/src/ rust:1.32 bash
fn main() {
for arg in std::env::args() {
println!("'{}'", arg);
}
}
use std::env;
fn main() {
// get the second argument, unwrap with a message
let first = env::args().nth(1).expect("please supply an argument");
let n: i32 = first.parse().expect("not an integer");
println!("first argument is {}", n);
}
fn main() {
let arr = [10, 20, 30, 40];
let first = arr[0];
println!("First {}", first);
for i in 0..4 {
println!("[{}] = {}", i, arr[i]);
}
println!("length {}", arr.len());
}
// read as: slice of i32
fn sum(values: &[i32]) -> i32 {
let mut res = 0;
for i in 0..values.len() {
res += values[i]
}
res
}
fn main() {
let arr = [10, 20, 30, 40];
// look at that &
let res = sum(&arr);
println!("sum {}", res);
}
fn main() {
let ints = [1, 2, 3];
let floats = [1.1, 2.1, 3.1];
let strings = ["hello", "world"];
let ints_ints = [[1, 2], [10, 20]];
println!("ints {:?}", ints);
println!("floats {:?}", floats);
println!("strings {:?}", strings);
println!("ints_ints {:?}", ints_ints);
}
use std::f64::consts;
fn main() {
let answer = 42;
assert_eq!(answer, 42);
// fully qualified name to find f64 PI
let x = 2.0 * std::f64::consts::PI;
let abs_difference = (x.cos() - 1.0).abs();
assert!(abs_difference < 1e-10);
// shorter way to get PI with the help of the `use` statement
println!("f64 PI is {}", consts::PI);
println!("abs_difference is {}, is small: {}", abs_difference, abs_difference < 1e-10);
}
use std::mem;
fn main() {
let a: u8 = 123; // unsigned 8 bit, 0..255
let b: i8 = -123; // signed 8 bit, -127..128
println!("a = {}", a);
println!("b = {}", b);
let mut c: i8 = 0; // mutable singed 8 bit
println!("c = {}", c);
c = 42;
println!("c = {}", c);
let mut d = 123456789; // let rust infer the memory size of the variable
println!("d = {}, size = {} bytes", d, mem::size_of_val(&d));
d = -1;
println!("d = {} after modification", d);
let z: isize = 123; // isize / usize - size of the memory address in the os
let size_of_z = mem::size_of_val(&z);
println!(
"z = {}, takes up {}, {}-bit os",
z,
size_of_z,
size_of_z * 8
);
let e: char = 'x';
println!("e = {}, size = {} bytes", e, mem::size_of_val(&e));
let f: f64 = 2.5; // dounble-precision 8 byts or 64 bits
println!("f = {}, size = {} bytes", f, mem::size_of_val(&f));
let g = false;
println!("g = {}, size = {} bytes", g, mem::size_of_val(&g));
}
use std::env;
use std::fs::File;
use std::io::Read;
fn main() {
let first = env::args().nth(1).expect("please supply a filename");
// the file is closed when the function ends and the file variable is dropped
let mut file = File::open(&first).expect("can't open the file");
let mut text = String::new();
file.read_to_string(&mut text).expect("can't read the file");
println!("file has {} bytes", text.len());
}
use std::env;
use std::fs::File;
use std::io;
use std::io::Read;
fn read_to_string(filename: &str) -> Result<String, io::Error> {
let mut file = match File::open(&filename) {
Ok(f) => f,
Err(e) => return Err(e),
};
let mut text = String::new();
match file.read_to_string(&mut text) {
Ok(_) => Ok(text),
Err(e) => Err(e),
}
}
// using the older try! macro
fn read_to_string_try(filename: &str) -> io::Result<String> {
let mut file = try!(File::open(&filename));
let mut text = String::new();
try!(file.read_to_string(&mut text));
Ok(text)
}
// using the newer ? operator
// if the result was an error, it will immediately return that error
// otherwise it returns the OK result
fn read_to_string_new(filename: &str) -> io::Result<String> {
let mut file = File::open(&filename)?;
let mut text = String::new();
file.read_to_string(&mut text)?;
Ok(text)
}
fn main() {
let file = env::args().nth(1).expect("please supply a filename");
let text = read_to_string(&file).expect("bad file man!");
println!("file had {} bytes", text.len());
let text = read_to_string_try(&file).expect("bad file man!");
println!("file had {} bytes", text.len());
let text = read_to_string_new(&file).expect("bad file man!");
println!("file had {} bytes", text.len());
}
fn sqr1(x: f64) -> f64 {
return x * x;
}
fn sqr2(x: i64) -> i64 {
x * x
}
fn abs(x: f64) -> f64 {
if x > 0.0 {
x
} else {
-x
}
}
fn clamp(x: f64, x1: f64, x2: f64) -> f64 {
if x < x1 {
x1
} else if x > x2 {
x2
} else {
x
}
}
fn factorial(n: u64) -> u64 {
if n == 0 {
1
} else {
n * factorial(n - 1)
}
}
fn by_ref(x: &i32) -> i32 {
*x + 1
}
fn modifies_arguments(x: &mut f64) {
*x = 1.0;
}
fn main() {
let res = sqr1(2.1);
println!("square of {} is {}", 2.1, res);
let res = sqr2(5);
println!("square of {} is {}", 5, res);
let res = abs(-123.1);
println!("abs of {} is {}", -123.1, res);
let res = clamp(25 as f64, 20 as f64, 23 as f64);
println!("clamp of {} is {}", 25, res);
let res = factorial(8);
println!("factorial of {} is {}", 7, res);
let i = 10;
let res1 = by_ref(&i);
let res2 = by_ref(&42);
println!("by ref {} is {}, by ref 42 is {}", i, res1, res2);
let mut res = 0.0;
println!("before modification res is {}", res);
modifies_arguments(&mut res);
println!("after modification res is {}", res);
}
fn main() {
for i in 0..5 {
println!("Hello {}", i);
}
}
fn main() {
for i in 0..5 {
if i % 2 == 0 {
println!("even {}", i);
} else {
println!("odd {}", i);
}
}
}
fn main() {
for i in 0..5 {
let even_odd = if i % 2 == 0 {"even"} else {"odd"};
println!("{} {}", even_odd, i);
}
}
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1, 101);
// println!("The secret number is {}", secret_number);
loop {
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
// shadow the guess variable to covert its type
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;
}
}
}
}
fn main() {
println!("Hello Rust!");
}
fn main() {
let mut iter = 0..3;
assert_eq!(iter.next(), Some(0));
assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), None);
let arr = [10, 20, 30];
for i in arr.iter() {
println!("array member: {}", i);
}
// slices will be converted implicityly to iterators
let slice = &arr;
for i in slice {
println!("slice member: {}", i);
}
}
fn good_or_bad(good: bool) -> Result<i32, String> {
if good {
Ok(42)
} else {
Err("bad".to_string())
}
}
fn main() {
println!("{:?}", good_or_bad(true));
// Ok(42)
println!("{:?}", good_or_bad(false));
// Err("bad")
match good_or_bad(true) {
Ok(n) => println!("Cool, I got {}", n),
Err(e) => println!("Huh, I just got {}", e),
}
// Cool, I got 42
}
fn main() {
// arithmetic
let mut a = 2 + 3 * 4;
println!("{}", a);
a = a + 1;
a -= 2; // -= += *= %=
println!("remainder of {} / {} = {}", a, 3, (a % 3));
// different types of powers
let a_cubed = i32::pow(a, 3);
println!("{} cubed is {}", a, a_cubed);
let b = 2.5;
let b_cubed = f64::powi(b, 3);
let b_to_pi = f64::powf(b, std::f64::consts::PI);
println!("{} cubed = {}, {}^pi = {}", b, b_cubed, b, b_to_pi);
// bitwise
let c = 1 | 2; // | OR & AND ^ XOR ! NOR
println!("1|2 = {}", c); // 01 OR 10 = 11 == 3_10
// shift
let two_to_10 = 1 << 10; // shift to left << or right >>
println!("2^10 = {}", two_to_10);
// logical
let pi_less_4 = std::f64::consts::PI < 4.0; // < > <= >= ==
let x = 5;
let x_is_5 = x == 5; // true
println!("pi < 4 == {}", pi_less_4);
println!("x == 5 {}", x_is_5);
}
fn main() {
let ints = [1, 2, 3, 4, 5];
let slice1 = &ints[0..2];
let slice2 = &ints[1..]; // open range
println!("ints {:?}", ints);
println!("slice1 {:?}", slice1);
println!("slice2 {:?}", slice2);
}
fn main() {
let ints = [1, 2, 3, 4, 5];
let slice = &ints;
let first = slice.get(0);
let last = slice.get(5);
println!("first {:?}", first);
println!("last {:?}", last);
println!(
"first is_some: {}, is_none: {}",
first.is_some(),
first.is_none()
);
println!(
"last is_some: {}, is_none: {}",
last.is_some(),
last.is_none()
);
println!("first value unwrap: {}", first.unwrap());
// check the value and unwrap
let maybe_last = slice.get(5);
let last = if maybe_last.is_some() {
// the precise type inside the some is &i32
// need to derefernece it to get a i32 value back
*maybe_last.unwrap()
} else {
-1
};
// unwrap_or is a shortcut to the above
// the type of the fallback value must match the array type
let last = *slice.get(5).unwrap_or(&-1);
println!("last value unwrap: {}", last);
}
fn main() {
let ints = [1, 2, 3, 4, 5];
let slice = &ints;
for s in slice.windows(2) {
println!("windows {:?}", s);
}
for s in slice.chunks(2) {
println!("chunks {:?}", s);
}
}
fn dump(s: &str) {
println!("str '{}'", s)
}
fn main() {
let text = "hello dolly"; // the string slice
let s = text.to_string(); // it's now an allocated string
dump(text);
dump(&s);
}
fn main() {
let text = "static";
let string = "dynamic".to_string();
let text_s = &text[1..];
let string_s = &string[2..4];
println!("slices {:?} {:?}", text_s, string_s);
}
fn main() {
let multilingual = "Hi! ¡Hola! привет!";
for ch in multilingual.chars() {
println!("'{}'", ch);
}
println!("");
println!("len {}", multilingual.len());
println!("count {}", multilingual.chars().count());
// long way
let maybe = multilingual.find('п');
if maybe.is_some() {
let hi = &multilingual[maybe.unwrap()..];
println!("Russian hi {}", hi);
}
// if interested in only one value, more concise
if let Some(idx) = multilingual.find('п') {
println!("Russian hi {}", &multilingual[idx..]);
}
// using match instead of if
match multilingual.find('п') {
Some(idx) => {
let hi = &multilingual[idx..];
println!("Russian hi {}", hi)
}
None => println!("couldn't find the greeting, Товарищ"),
}
}
fn main() {
let text = "the red fox and the lazy dog";
// split_whitespace returns an iterator
// collect need an explicit type
let words: Vec<&str> = text.split_whitespace().collect();
println!("words {:?}", words);
// or this way
// each slice in the vector is borrowing from the original string
// we allocate the space to keep the slices
let mut words = Vec::new();
words.extend(text.split_whitespace());
println!("words {:?}", words);
// iterator over chars and filter closure
let stripped: String = text.chars().filter(|ch| !ch.is_whitespace()).collect();
println!("stripped {}", stripped);
// same but with a loop
let mut stripped = String::new();
for c in text.chars() {
if !c.is_whitespace() {
stripped.push(c)
}
}
println!("stripped {}", stripped);
}
fn main() {
let mut s = String::new();
// initially empty
s.push('H');
s.push_str("ello");
s.push(' ');
s += "World!"; // short for `pust_str`
s.pop(); // remove the last char
assert_eq!(s, "Hello World");
}
fn array_to_str(arr: &[i32]) -> String {
let mut res = '['.to_string();
for v in arr {
res += &v.to_string();
res.push(',');
}
res.pop();
res.push(']');
res
}
fn main() {
let arr = array_to_str(&[10, 20, 30]);
let res = format!("hello {}", arr);
assert_eq!(res, "hello [10,20,30]");
}
fn main() {
let sum: i32 = (0..5).sum();
println!("sum was {}", sum);
let sum: i64 = [10, 20, 30].iter().sum();
println!("sum was {}", sum);
}
fn main() {
let mut v = Vec::new();
v.push(10);
v.push(20);
v.push(30);
let first = v[0]; // will panic if out-of-range
let maybe_first = v.get(0);
println!("v is {:?}", v);
println!("first if {}", first);
println!("maybe_first {:?}", maybe_first);
}
fn dump(arr: &[i32]) {
println!("arr is {:?}", arr)
}
fn main() {
let mut v = Vec::new();
v.push(10);
v.push(20);
v.push(30);
dump(&v);
let slice = &v[1..];
println!("slice is {:?}", slice);
}
fn main() {
let mut v1 = vec![10, 20, 30, 40];
v1.pop();
let mut v2 = Vec::new();
v2.push(10);
v2.push(20);
v2.push(30);
assert_eq!(v1, v2);
v2.extend(0..2);
assert_eq!(v2, &[10, 20, 30, 0, 1]);
}
fn main() {
let mut v1 = vec![1, 10, 5, 1, 2, 11, 2, 40];
v1.sort();
v1.dedup();
assert_eq!(v1, &[1, 2, 5, 10, 11, 40]);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment