Rust: Difference between revisions
Line 1,454: | Line 1,454: | ||
*Larger blocks cannot be placed on smaller blocks | *Larger blocks cannot be placed on smaller blocks | ||
I used the youtube video [[https://www.youtube.com/watch?v=rf6uf3jNjbo here]] which provided great graphics on how to solve this. Here is a function for 3 blocks and the steps required to achieve it.<br> | I used the youtube video [[https://www.youtube.com/watch?v=rf6uf3jNjbo here]] which provided great graphics on how to solve this. Here is a function for 3 blocks and the steps required to achieve it.<br> | ||
[[File:Hanoi.jpg |400px]] | [[File:Hanoi.jpg |400px]]<br> | ||
Took me a while to understand but the steps (not the moves were) | |||
*Move n-1 Discs from A to B using C | *Move n-1 Discs from A to B using C | ||
*Move a Disc from A to C | *Move a Disc from A to C |
Revision as of 00:11, 13 January 2024
Terms
- Fat Pointer Contains Address actual data and length
Cargo
Sample file
[package]
name = "hello_world"
version = "0.0.1"
authors = [ "Iain Wiseman iwiseman@bibble.co.nz" ]
Sample commands
cargo new hello_world --bin
cargo build
cargo run
Fundamental Data Types
Primitive types
Cam declare with size of type
let a:u8 = 123; // unsigned int 8 bits number immutable
let a:i8 = 123; // signed int 8 bits number immutable
let mut a:u8 = 123; // unsigned int 8 bits number mutable
Or without e.g.
let mut c = 123456789 // 32-bit signed i32
println!("c = {}", c);
Now variable based on OS e.g.
let z:isize = 123 // signed 64 bit if on 64 bit OS
Decimal
let e:f64 = 2.5 // double-precision, 8 bytes or 64-bits
Char
let x:char = 'x' // Note 4 bytes unicode
boolean
let g:bool = false; // Note 4 bytes unicode
Operators
Does not support -- and ++ but does support
a -= 2;
Remainder can be calculated using
a%3
Bitwise
let c = 1 | 2 // | OR
Shift
let two_to_10 = 1 << 10; // 1024
Logical of standard e.g.
let pi_less_4 = std::f64::consts::PI < 4.0; // true
Scope and shadowing
Curly braces keep scope
fn test()
{
{
let a = 5;
}
println!("Broken {a}");
}
Shadowing is fine though
fn test()
{
let a = 5;
{
let a = 10;
println!("10 {a}");
}
println!("5 {a}");
}
Constants
Standard const
const MEANING_OF_LIFE:u8 = 42;
Static const
static Z:i32 = 123;
Stack and Heap
Same a c++ i.e.
let y = Box::new(10);
println!("y = {}", *y);
Types
Tuples
Eezy peezy lemon squeezy
fn sum_and_product(x:i32,y:i32) -> (i32, i32)
{
(x+y, x*y)
}
fn main()
{
let sp = sum_and_product(3,4);
let (a,b) = sp;
let sp2 = sum_and_product(4,5);
// combine
let combined = (sp, sp2);
let ((c,d), (e,f)) = combined;
}
Arrays
Array sizes cannot grow in rust
Simple
let mut a:[i32;5] = [1,2,3,4,5];
// Or
let mut a = [1,2,3,4,5];
// Length
a.len()
// Assignment
a[0] = 321
// Printing
println!("{:?}", )
// Testing
if a == [1,2,3,4,5]
{
}
// Initialise
let b = [1,10]; // 10 array initialised to 1
Multi Dimension
Here is a two dimension array
let mtx:[[f32;3];2] =
[
[1.0, 0.0, 0.0],
[0.0, 2.0, 0.0],
];
Slices
A slice is a non-owning pointer to a block of memory. For example
// Create a vector
let v: Vec<i32> = {0..5}.collect();
// Now create a slice (reference)
let sv: &[i32]= &v;
// We create a slice with only some elements
let sv1: &[i32]= &v[2..4];
// Printing these will produce the same result
println!("{:?}",v);
println!("{:?}",sv);
// And the range
println!("{:?}",sv1);
Get the first 3 elements of an array
fn use_slice(slice: &mut[i32])
{
}
fn test()
{
let mut data = [1,2,3,4,5];
// Passes element 1-3 to use_slice as a reference
use_slice( &mut data[1..4]);
}
Strings
Basic String
let name = String::from("Iain");
Two types, static string and string type
let s = "hello";
// Cannot do
// let h = s[0]
// You can iterate as a sequence using chars e.g.
for c in s.chars()
{
println!("{}", c);
}
And now the mutable string in rust essentially an vector // Create a string
let mut letters = String::new();
Add a char
let a = 'a' as u8;
letters.push(a as char);
String to str
let u:%str = &letters;
Concatenation
let z = lettters + &letters
Other examples
let mut abc = "hello world".to_string()'
abc.remove(0);
abc.push_str("!!!");
abc.replace("ello","goodbye")
Control Flow
if statement
Same as C++ except no brackets
if temp > 30
{
println!("Blah");
}
else if temp < 10
{
println!("Blah");
}
else
{
println!("Blah");
}
Elvis is like
let a = if temp > 30 {"sunny"} else {"cloud"}
While and Loop
While
Same as C++ except no brackets
while x < 1000
{
}
There is support for continue and break
Loop
Loop is while true
loop
{
if y == 1 << 10 { break; }
}
For Loop
A bit like kotlin loops (I think)
for x in 1..11
{
println!("x = {}",x);
}
You can get position in series as well
for (pos,x) in (1..11).enumerate()
{
println!("x = {}, pos = {}",x, pos);
}
Rust Principles
Ownership
Move
Move is when you assign a value to another variable. If we try and use a variable after the move we will get an error.
let v = vec![1,2,3]
let v2 = v;
println!("{:?}",v2)
println!("{:?}",v) // Error
Copy
When we copy something me make a new thing. They is not the same a let a = b, which is assignment. Copy means we duplicate the underlying data of the type. For primitives a copy is implemented by default. This is because the primitive has a know size. E.g. u32, bool etc. If you want to be able to copy a non primitive you need to add the derive macro. Note Clone must also be specified
#[derive(Copy, Clone)]
enum Direction {
North,
East,
South,
West,
}
#[derive(Copy, Clone)]
struct RoadPoint {
direction: Direction,
index: i32,
}
Clone
Clone is a method you can call on a struct if you want a second instance and not move the ownership. Here is an example. The struct obviously needs to implement the Copy/Clone macro. Cloning clearly increases the memory used.
let v = vec![1,2,3]
let v2 = v.cone();
println!("{:?}",v)
println!("{:?}",v2)
References
So references are like C++ references, but for rust this means you can pass the ownership during function call
main() {
let mut s = String::from("Hello");
change_string(&mut s);
}
fn change_string(some_string: &mut String) {
some_string.push_str(", world!");
}
Note for returning a Reference
If we are returning a reference we must be returning a parameter as all local variables are destroyed. (Clearly Rust is not going to allow new MyMemory(6502)
Structs
General
There are 3 types of structs, name, tuple and unit structs
- Named
- Tuples
- Unit
Name Struct
struct User
{
active: bool,
username: String,
sign_in_count: u32
}
let user1 = User{active: true, username: String::from("Biil"),
sign_in_count: 0};
println!("{}", user1.username);
...
fn build_user(username: String) -> User {
User {
username,
active:true,
sign_in_count: 1
}
}
Tuple Struct
Tuple structs use the order in which declared to assign.
struct Coordinates{i32,i32,i32};
let coords = Coordinates{1,2,3};
Unit Struct
These are used to mark the existence of something
struct UnitStruct;
let a = UnitStruct{}
The example shown was when you are implementing a trait (interface) but the properties were not required for this type. So given a trait for Area, Square uses size but Point does not have an area as it is zero
trait AreaCalculator {
fn calc_area(&self) => f64
}
struct Square {
size: f64
}
struct Point;
impl AreaCalculator for Square {
fn calc_area(&self) -> f64 {
self.size * self.size
}
}
impl AreaCalculator for Point {
fn calc_area(&self) -> f64 {
0.0
}
}
We can use it for error
struct DivideByZero;
fn divide(nom: f64, den: f64) -> Result<f64, DivideByZero> {
if den != 0.0 {
Ok(nom/den)
} else {
Err(DivideByZero)
}
}
Example Structs
struct Point
{
x: f64,
y: f64
}
fn main()
{
let p = Point { x: 30.0, y: 4.0 };
println!("point is at ({},{})", p.x, p.y)
}
Methods on Structs
Methods on struct require the first argument to be self
Example Method
Add method len to struct
struct Line
{
start: Point,
end: Point
}
// Declare impl using the keyword impl. Not ends with no semi colon.
impl Line
{
fn len(&self) -> f64
{
let dx = self.start.x - self.end.x;
let dy = self.start.y - self.end.y;
(dx*dx+dy*dy).sqrt()
}
}
Changing an attribute
To change an attribute and ensure you do not break the borrowing rules we do
struct Square {
width: u32,
height: u32
}
impl Square
{
fn area(&self) -> u32 {
self.width * self.height
}
fn change_width(&mut self, new_width: u32) -> Self
{
self.width = new_width;
}
}
...
main() {
...
let mut sq = Square(width:5, height: 5);
sq.change_point(10)
}
Lifetime
What are Dangling References
The code below will not compile. This is because x goes out of scope before r. I am guessing this is what is known as a dangling reference.
fn test() {
let r;
{
let x = 5;
r = &x; // Error `x` does not live long enough
}
log::info!("{}",r);
}
Lifetime Annotations
Not sure which way around these are but you specify lifetime annotations on functions and structs and they imply information to the compiler on how long the parameters will live for.
Three Rules of Lifetimes
Here are the rules but we also need to understand what they apply to. Kind of chicken and egg. An example is give below which is broken because these rules are not followed.
- Each Parameter that is a reference gets its own lifetime parameter
- If there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters
- If there are multiple input lifetime parameters, but one of them is &self or &mut self the lifetime is assigned to all output lifetime parameters
Example (Broken code)
Here is an example of code which cannot be compiled without lifetime being specified.
pub struct TestStruct {
length: i32,
}
fn test2(x: &TestStruct, y: &TestStruct) -> &TestStruct { // Missing lifetime specifier
if x.length > y.length {
x
}
else {
y
}
}
Adding Annotations
To do this we specify annotations. The extension in vscode does this for us using the quick fix. The code now looks like this
fn test2<'a>(x: &'a TestStruct, y: &'a TestStruct) -> &'a TestStruct {
if x.length > y.length {
x
}
else {
y
}
}
My inference from this is that all parameters have the same lifetime.
Lifetime Annotations for Structs
Structs can also have lifetime annotations. If you specify a reference then you will need to specify a lifetime annotation. In the example below when we make the struct of type MyString we need to make sure that str1 does not go out of scope while x of type MyString exists otherwise it would refer to something no longer in scope.
// Without lifetime annotation will not compile.
// struct MyString {
// text: &str,
// }
struct MyString<'a> {
text: &'a str,
}
fn main() {
let str1 = String::from("This is my String);
let x = MyString(text: str1.as_str());
}
Static Lifetimes
We can also have lifetimes for statics.
let s: &'static str = "I live forever";
Doing this means the values are stored in the binary.
Enums
Example 1 with Method
Seems a bit C++ but...
enum Pet {dog, cat, fish}
And now lets add a method as we do with structs. Note for this method we are returning something and with rust all locals are destroyed on return so we need to specify a lifetime.
enum Pet {dog, cat, fish}
impl Pet {
fn what_am_i(self) -> &'static str {
match self {
Pet::dog => "I am a dog",
Pet::cat => "I am a cat",
Pet::fish => "I am a fish",
}
}
}
Example 2
enum Color {
Red,
Green,
Blue
}
fn main()
{
let c:Color = Color::Red;
match c
{
Color::Red => prinln!("Color is Red");
Color::Green => prinln!("Color is Green");
}
}
Example 3 with Types
enum Color {
Red,
Green,
Blue,
RgbColor(u8,u8,u8) // Tuple
CmykColor{cyan:u8, magenta:u8, yellow:u8, black:u8,} // Struct
}
fn main()
{
let c:Color = Color::RgbColor(10,0.0);
match c
{
Color::Red => prinln!("Color is Red");
Color::Green => prinln!("Color is Green");
Color::RgbColor(0,0,0) => prinln!("Color is Black");
Color::RgbColor(r,g,b) => prinln!("Color is {},{},{}", r,g,b);
}
let d:Color = Color::CmykColor(cyan:0, magenta:0, yellow:0, black:0);
match d
{
Color::Red => prinln!("Color is Red");
Color::Green => prinln!("Color is Green");
Color::RgbColor(0,0,0) => prinln!("Color is Black");
Color::CmykColor(cyan:_, magenta:_, yellow:_, black:255) => prinln!("Black");
}
}
Option<T> Enum
This enum if provided for us by rust and looks like this
enum Option<T> {
None,
Some(T)
}
We would choose this type when we have a case where there could be a value or not. I guess this is the equivalent of string? in Typescript where we may or may not have a value. In rust we use match to support this type.
let some_number = Some(5);
let some_string = Some("a string");
let nothing: Option<i32> = None;
Pattern Matching
Match is Exhaustive approach to pattern matching. I.E. you need to specify something for every option you are using match for. However you can include a default. I find this a great approach
Examples
Simple Match
match x
{
0 => "zero"
1 | 2 => "one or two"
9...11 => "lots of" // two dots does not include end value (exclusive)
_ if(blahh) => "something"
_ => "all others"
}
Here is another example.
let country = match country_code
{
44 => "uk",
46 => "sweden",
7 => "russia"
1...999 => "unknown" // other triple dot does include end value (inclusive)
_ => "invalid" // invalid
};
Match on Tuples
This is an exert from [Game of Life]. We can match on tuples, and I imagine other types too. For tuples you can specify a value or compare to a value. Note the use of otherwise
let next_cell = match (cell, live_neighbors) {
// Rule 1: Any live cell with fewer than two live neighbours
// dies, as if caused by underpopulation.
(Cell::Alive, x) if x < 2 => Cell::Dead,
// Rule 2: Any live cell with two or three live neighbours
// lives on to the next generation.
(Cell::Alive, 2) | (Cell::Alive, 3) => Cell::Alive,
// Rule 3: Any live cell with more than three live
// neighbours dies, as if by overpopulation.
(Cell::Alive, x) if x > 3 => Cell::Dead,
// Rule 4: Any dead cell with exactly three live neighbours
// becomes a live cell, as if by reproduction.
(Cell::Dead, 3) => Cell::Alive,
// All other cells remain in the same state.
(otherwise, _) => otherwise,
};
Operators and Symbols
Found in Table B-1 here [Operators and Symbols]
- [Range]: 1..10
- [RangeFrom]: 1..
- [RangeTo]: ..10
- RangeFull: ..
- RangeInclusive: 1..=10
- RangeToInclusive: ..=10
Option <T> and if let
Used to avoid null or invalid values. This was used in things where the value might be present. Maybe command line arguments where some were provide or none were provided. Lets to the classic divide by zero.
let x = 3.0
let y = 0.0 // Divide by zero
let result:Option<f64> =
if y != 0.0 { Some(x/y) } else { None };
// Using match
match result {
Some(z) => println!("Goody result"),
None => println!("No result")
}
// Using if let
if let Some(z) = result { println!("z = {}", z); }
More if let
Here is another example
let mut stack = Vec:new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop() {
println!("{}", top);
}
Generics
This is very similar to C++ Templates and TypeScript Generics
struct Point<T>
{
x: T,
y: T
}
fn generics()
{
let a:Point<i32> = Point {x: 0, y: 4}
}
Traits
Traits are similar to interfaces in java and c#
Defining a Traits
trait Animal
{
fn create(name:&'static str);
fn name(&self) => &'static str;
fn talk(&self)
{
println!("{} cannot talk",self.name());
}
}
Implement a Trait
Here we create a struct which will implement out trait. Note we do not have to implement all functions if the trait provides a default implementation
Implement a Trait for Animal
struct Human
{
name: &'static str;
}
impl Animal for Human
{
fn create(name:&'static str) -> Human
{
Human{name: name}
}
fn name(&self) -> &'static str
{
self.name
}
// override default
fn talk(&self)
{
println!("{} can talk",self.name());
}
}
Implement a Trait for Cat
Here we implement the Animal Trait for Cat
struct Cat
{
name: &'static str;
}
// Implement interface
impl Animal for Cat
{
fn create(name:&'static str) -> Cat
{
Cat{name: name}
}
fn name(&self) -> &'static str
{
self.name
}
// override default
fn talk(&self)
{
println!("{} says meeow",self.name());
}
}
Usage
And using the structs.
let h:Human = Animal::create("John");
let c:Cat = Animal::create("John");
Provided Traits
Drop Trait
Drop trait is called automatically to free up resources but you can write your own e.g. for the example above we could write
impl Drop for Course {
fn drop(&mut self) {
println("Dropping")
}
}
Clone Trait
Like the drop trait we can implement our own. Refer to the clone trait for this.
Copy Trait
We can either specify #[derive(Copy, Clone)] or implement our own. There are restrictions on this
From and Into Trait
This allow us to convert from one type to another
fn into(self) -> T
fn from(T) -> Self
fn try_into(self) -> Result<T, Self: Error>
fn try_from(value: T) -> Result<Self, Self: Error>
Passing Trait as Parameters
So here is an example of two structs with overview implement,one using the trait default implementation, the other its own. We can use the trait similar to a pointer to a function.
Example
trait Overview {
fn overview(&self) -> String {
format("This is a rust course")
}
}
struct Course {
headline: String,
author: String
}
struct AnotherCourse {
headline: String,
author: String
}
impl Overview for Course {
}
impl Overview for AnotherCourse {
fn overview(&self) -> String {
format("{}, {}", self.author, self.headline)
}
}
We can use the overview trait a a fn parameter with
fn call_overview(item: &imp) {
println("Overview: {}", item.overview())
}
// OR
fn call_overview<T: Overview>(item: &T) {
println("Overview: {}", item.overview())
}
Trait Bounds
The example above has two ways to achieve the same thing. If we constrain what this allowed, this is called trait bounds. Lets add a second parameter.
// This example only forces the struct to implement the trait
// fn overview(item1: &imp Overview, item2: &imp Overview)
// But this force the struct to be of the same type
// fn overview<T: Overview>(item1: &T, item2: &T)
We can add more constraints with the + operator. Now they need the second trait.
// fn overview(item1: &imp Overview + AnotherTrait, item2: &imp Overview + AnotherTrait)
// fn overview<T: Overview + AnotherTrait>(item1: &T, item2: &T)
Here we have an example of ensuring that the incoming parameters are constrained to be of type T
struct Pointy<T> {
x: T,
y: T,
}
impl <T> Add for Pointy <T>
where T: Add<Output = T>
{
type Output = Self;
fn add(self, other: Self) -> Self {
Self {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
Common Collections
Vectors
Same a c++
let mut a = Vec::new()
a.push(1);
a.push(2);
a.push(3);
// Print
println!("a[0] {}", a[0]);
// We can create vector with initial capacity
let mut b = Vec::<i32>::with_capacity(2);
// We can initialize using an iterator values of 0-4
let c: Vect<i32> = (0..5).collect();
// Using get returns a option
match a.get(3333)
{
...
}
// Removing, pop returns an option
let last_elem = a.pop();
// Using the option type iterating over vector to print it
while let Some(x) = a.pop()
{
println!("x = {}",x);
}
Binary Heap
This make sure the highest is at the top. It has a peek function to allow you to peek at values.
let mut bHeap = BinaryHeap::new();
bHeap.push(1);
bHeap.push(18);
bHeap.push(20);
bHeap.push(5);
bHeap.pop();
println!("{:?}", bHeap); // 20
Maps
Not discussed
Sets
Not discussed
Error Handling
Panic
Panic happens when unhandled error occurs. This happens for instance when we access out of bounds array. We can get a backtrace by setting the environment export RUST_BACKTRACE=-1
Result Enum
The Result an enum which has two generics Result<T, E> where T is the type an E is the error. In rust we use the match to determine what to do.
let file = File::Open("Does_not_exist.mp3");
let file match file {
Ok(file) => file,
Err(error) => panic("Error: {:?}", error),
};
Testing
We specify the cfg option and use the assert library
fn sqrt(number: f64) -> Result<f64, String> {
if number >= 0.0 {
Ok(number.powf(0.5))
} else {
Err("negative floats don't have square roots".to_owned())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sqrt() -> Result<(), String> {
let x = 4.0;
assert_eq!(sqrt(x)?.powf(2.0), x);
Ok(())
}
}
We can run these with
cargo test
Handling CLI Arguments
Clap seems to be the approach for this, make sure you install the macros otherwise errors will show on the derive macro.
cargo add clap --features derive
And the code. The shows how to replace text from a file to another file.
use regex::Regex;
use text_colorizer::*;
use clap::Parser;
use std::fs;
#[derive(Parser,Debug)]
struct Args {
#[clap(short, long)]
pattern: String,
#[clap(short, long)]
replace: String,
#[clap(short, long)]
input_file: String,
#[clap(short, long)]
output_file: String,
}
fn replace (pattern: &str, replace: &str, data: &str) -> Result<String, regex::Error> {
let regex = Regex::new(pattern)?;
Ok(regex.replace_all(data, replace).to_string())
}
fn read_write_file(args: Args) {
let data = match fs::read_to_string(&args.input_file) {
Ok(data) => data,
Err(err) => {
eprintln!("{} failed to read from file {}: {:?}",
"Error".red().bold(),
args.input_file, err);
std::process::exit(1);
}
};
let replace_data = match replace(&args.pattern, &args.replace, &data) {
Ok(data) => data,
Err(err) => {
eprintln!("{} failed to replace text {:?}",
"Error".red().bold(),
err);
std::process::exit(1);
}
};
match fs::write(&args.output_file, replace_data) {
Ok(_) => {},
Err(err) => {
eprintln!("{} failed to write to file {}: {:?}",
"Error".red().bold(),
args.output_file, err);
std::process::exit(1);
}
};
}
fn main_body(args: Args) -> Result<(), ()> {
// Your main body here
println!("input_file {}", args.input_file);
println!("output_file {}", args.output_file);
read_write_file(args);
Ok(())
}
fn main() {
let args = Args::parse();
main_body(args).unwrap_or_else(|_| {
eprintln!("Error");
std::process::exit(1);
});
}
Closures
Closures are functions you which you can use the available scope to with that function. They look like anonymous function in typescript
let x: i32 = 5;
let adder = |a| a + x;
let b = adder(12);
print!("b {}", b)
The adder closure takes a parameter and captures existing scope at creation to produce the answer 17.
Three traits exist for closures
- Fn
- FnOnce
- FnMut
When we look at out closure we need to consider the operations inside the closure. The examples given were
// || drop(v) FnOnce as we only drop once
// |args| v.contains(arg) Fn as does not modify
// |args| v.push(arg) FnMut as it does modify
Iterators
Nothing fancy here except we have to make the iterator mut (not surprising but worth mentioning). An iterator is any type which implements the iter trait and an iterable is a type that implements into iterator.
let vec = [1, 2, 3];
let mut iter = vec.iter();
while let Some(i) = iter.next() {
println!("i {}", i);
}
Just like Typescript we can now use closures like the map, filter functions in typescript. E.g. given a vector of times we can filter on property
items.into_iter().filter(|i|i.name == search_name).collect()
We can implement our own iterator on our struct like below
struct Range {
start: u32,
end: u32
}
impl Iterator for Range {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.start >= self.end {
return None
}
let result = Some(self.start);
self.start += 1;
result
}
}
Pointers
This is the same a C++ with some rusty new language. The Box is a pointer type that uniquely owns a heap allocation of type
Box Type
This the basic pointer
let t = (12, "eggs");
let b = Box::new(t);
println!("b {:?}", b); // (12, "eggs")
RC Type
This allows multiple pointers to the same thing in memory. For example
let s1 = Rc::new(String::from("Pointer));
let s2 = s1.clone();
let s3 = s2.clone();
println("{},{},{}",s1,s2,s3) // Pointer, Pointer, Pointer
RefCell
A RefCell is another way to change values without needing to declare mut. It means "reference cell", and is like a Cell but uses references instead of copies, Rust refers to Interior mutability which seems to say it is a pattern where the user is allow to modify data despite the data not having a mut associated with it. The function of RefCell allow us to modify when we dereferenced.
use std::{cell::RefCell};
struct Flagger {
is_true: RefCell<bool>,
}
// borrow returns Ref<T>
// borrow_mut returns RefMut<t>
let flag = flagger{ is_true: RefCell::new(true)};
// let reference = flag.is_true.borrow();
// println!("{}", reference);
let mut mut_ref = flag.is_true.borrow_mut();
*mut_ref = false;
Note if we want to use it twice, i.e. uncomment the println we have to wrap the RefCell with RC
// In struct
is_true: Rc<RefCell<bool>>
// Initialize
let flag = flagger{ is_true: Rc::new<RefCell::new(true))};
Concurrency
Thread Join
Threads are similar to C++ and C#. Let do a basic join
let handle = thread::spawn(move || {
println!("Hello from a thread!")
});
handle.join().unwrap();
println!("Hello Main!")
Not threads do not always finish in the order created
v.into_iter().for_each(|e| {
thread_handles.push(thread::spawn(move || println!("Thread {}",e)));
});
thread_handles.into_iter().for_each(|handle| {
handle.join().unwrap();
});
// Thread 2
// Thread 1
// Thread 3
Channels
This is like channels in kotlin. These live in the mpsc namespace which stands for multi producer single consumer. The value being sent is taken on send and receive. I.E. you cannot use the value being sent after send.
Example One Producer
use std::sync::mpsc::channel;
use std::thread;
let (sender, receiver) = channel();
// Spawn off an expensive computation
thread::spawn(move|| {
sender.send(expensive_computation()).unwrap();
});
// Do some useful work for awhile
// Let's see what that answer was
println!("{:?}", receiver.recv().unwrap());
Example Two Producer
We can have multiple producers and one receiver. To ensure that the receiver is not overwhelmed rust provides a sync_channel method on the mpsc. When used the sender will block when the queue if full and automatically continue when reduced.
let (producer1, receiver) = sync_channel(1000);
let producer2 = producer1.clone();
// Send from Producer 1
thread::spawn(move|| {
let vec = vec![String::from("transmitting"), String::from("hello"), String::from("world"),];
for val in vec {
producer1.send(val).unwrap();
}
});
thread::spawn(move|| {
let vec = vec![String::from("producer2"), String::from("hello"), String::from("world 2"),];
for val in vec {
producer2.send(val).unwrap();
}
});
// Send from Producer
for received in receiver {
println!("Got: {}", received);
}
Sync and Send Type Traits
Send
Types that implement send are safe to pass by value to another thread and moved across threads. Almost all types implement send but there are exceptions, e.g. Rc however the Atomic Reference Counter (Arc) can be. This did not compile for me if I tried to use Rc with the error std::rc::Rc<std::string::String>` cannot be sent between threads safely within `{closure@src/main.rs:116:24: 116:31}`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<std::string::String> but it did on the course.
Sync
Types that implement sync are safe to pass by non mutable reference to another thread. These types can be shared across threads.
Introduction
These a like c++ mutexes. You need to get and release a lock when using the resource. It was stressed the the atomic reference counter Arc (no Rc) is used for sharing resources across threads. Mutexes are used for mutating (modifying) data that is shared across threads.
Deadlocks
Below is an example of a deadlock where the same thread asks for the lock without releasing. Note you can release the mutex with drop on the guard (numMutexGuard). I also noticed if you did not use the value then the code did complete - maybe an optimizer.
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move|| {
let mut numMutexGuard = counter.lock().unwrap();
// *numMutexGuard += 1;
// std::mem::drop(numMutexGuard);
let mut numMutexGuard2 = counter.lock().unwrap();
*numMutexGuard2 += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
Poisoned Mutex
This is a term which means when a thread is executing and has a lock but panics. The mutex is hanging or poisoned. In rust we can actually recover from this though I suspect this is very undesirable. Here is the sample code where we match on poisoned.
let lock = Arc::new(Mutex::new(0));
let lock2 = Arc::clone(&lock);
let _ = thread::spawn(move || {
let _guard = lock2.lock().unwrap(); // acquire lock
panic!("thread1"); // mutex is not poisoned
}).join();
let mut guard = match lock.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
*guard += 1;
println!("lock value: {}", *guard);
Rayon
Quick example of rayon for parallelization with Rayon. Was a little unhappy with the changing of signatures for reduce in rayon but ho-hum. This took 6550ms single threaded and 186ms multi-threaded.
fn factoral (n: u32) -> BigUint {
if n == 0 || n ==1 {
BigUint::from(1u32)
} else {
// Reduce in Typescript is array.reduce((acc, next_value) => acc * next_value, 1)
(1..=n).map(BigUint::from).reduce(|acc, next_value| acc * next_value).unwrap()
// new way
// n * factoral(n - 1)
}
}
fn factoral_with_rayon (n: u32) -> BigUint {
if n == 0 || n ==1 {
BigUint::from(1u32)
} else {
(1..=n).into_par_iter().map(BigUint::from).reduce(|| BigUint::from(1u32), |acc, x| acc * x)
}
}
main() {
let mut now = std::time::Instant::now();
factoral(80000);
println!("factoral took {} seconds", now.elapsed().as_millis());
now = std::time::Instant::now();
factoral_with_rayon(80000);
println!("factoral with rayon took {} seconds", now.elapsed().as_millis());
}
Asyncronous
Async and Await (Future Trait)
This is just like promises. I learned a few things doing this. Renaming namespaces can be done using as. For the asynchronous work with thread still present there was a lot of crossover between the two. Note not all code listed so additional use statements demonstrate the renaming requirement.
use async_std::{prelude::*, io as async_io, task as async_task, fs as async_fs, fs::File as async_file};
use std::{cell::RefCell, fs, thread, sync::{mpsc::sync_channel, Arc, Mutex}};
async fn read_file(path: &str) -> async_io::Result<String> {
let mut file: async_file = async_fs::File::open(path).await?;
let mut contents = String::new();
file.read_to_string(&mut contents).await?;
Ok(contents)
}
fn main() {
let task = async_task::spawn(async {
let result = read_file("Cargo.toml").await;
match result {
Ok(k) => println!("contents {}", k),
Err(err) => println!("error {}", err),
}
});
async_std::task::block_on(task);
println!("Task stopped");
}
Big O Notation
General
Big O notation is a mathematical notation that describes the limiting behavior of a function when the argument tends towards a particular value or infinity. This graph tries to convey how the performance of an algorithm is impacted based on the O(n) value. Other references are Big_o_notation and [here]
They talk about approaches used.
- Experimental
- Theoretical
Experimental
Experimental analysis involves running a program and measuring how long it takes to complete. The problem with this approach is that programs run non-deterministically and at different speeds depending on the hardware, compiler, programming language, and other factors.
Theoretical
The alternative is theoretical analysis. One approach to theoretical analysis is to approximate the running time by counting the number of steps an algorithm takes for a given input.
Recursion and Factorial
General
Recursion is when we use a function multiple times but change the input. An example of recursion is factoral where
n! = n * n-1 * n -2 * n-2... n! = n * (n-1)!
when doing recursion we must specify a any base cases and a termination condition. For factorial this would be line 12
#include<stdio.h>
long int multiplyNumbers(int n);
int main() {
int n;
printf("Enter a positive integer: ");
scanf("%d",&n);
printf("Factorial of %d = %ld", n, multiplyNumbers(n));
return 0;
}
long int multiplyNumbers(int n) {
if (n>=1)
return n*multiplyNumbers(n-1);
else
return 1;
}
Call Stack
When you use recursion we need to be careful that we do not have a stackoverflow.
Base Cases
Base Case is defined as the condition in Recursive Function, which tells the function when to stop. It is the most important part of every Recursion, because if we fail to include this condition it will result in INFINITE RECURSIONS.
Fibonacci
Here is the Fibonacci example which seems to be the darling of all CS students. Here there are 2 bases cases
- base case when n = 0
- base case when n = 1
I think the match operator is a great way to express recursive functions
fn fibonacci(n: u32) -> u32 {
match n {
0 => 1,
1 => 1,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
fn main() {
println!("Fibonacci generator");
println!("{}", fibonacci(1));
println!("{}", fibonacci(3));
println!("{}", fibonacci(5));
}
Palindrome
Here we decide what the parameters are for the base cases
fn is_palindrome(s: &str, start: usize, end: usize) -> bool {
if start >= end {
return true;
}
if s.chars().nth(start) != s.chars().nth(end) {
return false;
}
is_palindrome(s, start + 1, end - 1)
}
fn main() {
let answer1 = is_palindrome("racecar", 0, 6);
println!("{}", answer1);
let answer2 = is_palindrome("xracecar", 0, 7);
println!("{}", answer2);
}
Tower of Hanoi
This was a more challenging puzzle to demonstrate recursive functions. Here the object of the game is to move the blocks from tower one to tower three and replicate the order. The rules are
- Only one block at a time
- Larger blocks cannot be placed on smaller blocks
I used the youtube video [here] which provided great graphics on how to solve this. Here is a function for 3 blocks and the steps required to achieve it.
Took me a while to understand but the steps (not the moves were)
- Move n-1 Discs from A to B using C
- Move a Disc from A to C
- Move n-1 Discs from B to C using A
Macros
I did macros using YouTube and the course. The first example is the course, the second is YouTube.
Greatest Common Denominator Macro
Here is the simple example.
macro_rules! gcd {
($a: expr, $b: expr) => {
{
let mut a = $a;
let mut b = $b;
while b != 0 {
let t = b;
b = a % b;
a = t;
}
a
}
};
}
main() {
print!("gcd is {} ", gcd!(14,28));
}
Declarative Macros
This seems similar to C++ with improvements. Need to learn the syntax but this should be enough for me to get going. Here is a basic macro.
#[macro_export]
macro_rules! my_macro {
() => {
{ // Do Stuff }
};
}
Export allows the macro to be seen, macro_rules! is the syntax to say I am a macro then the name of the macro. We can now write an arrow function similar to typescript. This has curly braces around whatever we are returning.
This example appends a "# " to the text passed. The function arguments are $var and in this case the type is literal. These are known as fragment specifiers. And a list can be found [here]
#[macro_export]
macro_rules! header {
($var:literal) => {
{concat!("# ", $var)}
};
}
fn main() {
let fred = header!("fred");
println!("Hello, world! {}", fred);
}
We can test this using the testing framework offered by rust and running cargo test
#[cfg(test)]
mod tests {
#[test]
fn test_header() {
let val = header!("Hello");
assert_eq!(val, "# Hello");
}
}
A more complicated macro maybe one that takes a list of values. E.g. ulist!["a","b","c"]. First we define a parameter with multiple inputs, to do this start with $(), separator , and repetition operator +. A list of repetition operators may be found [here]
macro_rules! ulist {
($(),+) => {
};
}
To emphasise the separator I have changed it to a % to make it clear how it is used.
#[macro_export]
macro_rules! ulist {
($($var:literal)%+) => {
{ concat!( $("item:", $var, "\n",)+ ).trim_end() }
};
}
Basically the example concatenates the values together and using .trim_end() removes the last "\n". Left it in as it may remind me how to do this. And here is the test.
#[test]
fn test_ulist() {
let val = ulist!("foo"%"bar");
assert_eq!(val, "item:foo\nitem:bar");
}
Procedural Macros
The previous macros were like C++ macros which match against patterns. Procedural macros are like functions, they take code as input, operate on it and then output code. There are three types of these
- Custom #[derive] macros.
- Attribute-like macros.
- Function-like macros
We are going to look at the derived as this is what I have come across and need to know
This type of macro takes code as input and outputs code.
- Create a library crate called billprod_macro
- Create a procedural macro crate inside billprod_macro (billprod_macro_derive)
Macro Library
This just defines the trait we are going to build. In the lib.rs we declare this trait (interface)
pub trait BillProdMacro {
fn bill_prod_macro();
}
Macro Derive Library
This lives inside of the macro library. For the procedural macro we need to define the Cargo.toml as a proc-macro and add dependencies syn and quote.
[package]
name = "billprod_macro_derive"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro = true
[dependencies]
syn = "1.0.75"
quote = "1.0.9"
For the code, we have something called TokenStream which is capable of parsing rust code. We write a function to receive the code, do something, and a function to output the code. My understanding is these are the same for most macros. We annotate it with the name of the macro #[proc_macro_derive(BillProdMacro)]
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn;
#[proc_macro_derive(BillProdMacro)]
pub fn billprod_macro_derive(input: TokenStream) -> TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast = syn::parse(input).unwrap();
// Build the trait implementation
impl_billprod_macro(&ast)
}
The implementation takes the code (ast) prints out text with the type.
fn impl_billprod_macro(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let gen = quote! {
impl BillProdMacro for #name {
fn bill_prod_macro() {
println!("Hello, world! My name is {}", stringify!(#name));
}
}
};
gen.into()
}
Using the Macro
We need both packages. As they are not published we must reference them on disk
...
[dependencies]
billprod_macro = { path = "../billprod_macro" }
billprod_macro_derive = { path = "../billprod_macro/billprod_macro_derive" }
So now we can use them in the code.
use billprod_macro::BillProdMacro;
use billprod_macro_derive::BillProdMacro;
#[derive(BillProdMacro)]
struct BillCake;
....
fn main() {
BillCake::bill_prod_macro();
}
This outputs
Hello, world! My name is BillCake
WASM
Web assembly for rust is done via
Functions
Functions and Arguments
No surprises
fn print_value(x: i32)
{
println("value = {}", x);
}
Pass by reference
fn increase(x: &mut i32)
{
*x = 1;
}
Return value, note no semicolon
fn product(x: i32, y: i32) -> i32
{
x * y
}
Return two values, note no semicolon
fn product(x: i32) -> i32
{
if x == 10 {
123
}
321
}
Higher-order functions
Not sure what this is, seems like just a way to chain written functions together like lamba. Here is the given example.
fn is_even(x: u32) -> bool
{
x%2 == 0
}
fn main()
{
// Method without HOF
let limit = 500;
let mut sum = 0;
for i in 0..
{
let isq = i*i;
if isq > limit { break; }
else if is_even(isq) { sum += isq; }
}
println!("loop sum = {}", sum);
// HOF way
let sum2 =
(0...).map(|x| x*x)
.take_while(|&x| x < limit)
.filter(|x| is_even(*x))
.fold(0, |sum,x| sum+x);
println!("hof sum = {}", sum2);
}
Odds and Ends
Consuming Crates
Crates is like nuget [1]
[package]
name = "mypackage"
version = "0.1.0"
author = " ["Iain Wiseman iwiseman@bibble.co.nz"]
[dependencies]
rand = "0.3.12"
And the usage
extern crate rand; // Package
use rand::Rng; // Namespace
fn main() {
let mut rng = rand::thread_rng();
let b:bool = rng.gen();
}
Building Crates and Modules
Module example e.g. src/lib.rs
pub mod greetings
{
pub mod english
{
pub fn hello() -> {"hello"->to_string() }
pub fn goodbye() -> {"goodbye"->to_string() }
}
pub mod french
{
pub fn hello() -> {"bonjour"->to_string() }
pub fn goodbye() -> {"au revoir"->to_string() }
}
}
For modules within modules you can make directories. For above this would be greetings\english and greetings\french. So we could have greetings\english\english.rs
pub fn hello() -> {"hello"->to_string() }
pub fn goodbye() -> {"goodbye"->to_string() }
greetings\lib.rs
pub mod greetings
{
pub mod english;
pub mod french
{
pub fn hello() -> {"bonjour"->to_string() }
pub fn goodbye() -> {"au revoir"->to_string() }
}
}
We need a package file for it
[package]
name = "phrases"
version = "0.1.0"
author = " ["Iain Wiseman iwiseman@bibble.co.nz"]
And then cargo build should work. To use the package you will need to specify the path to the package in the cargo file where used. e.g.
[package]
name = "mypackage"
version = "0.1.0"
author = " ["Iain Wiseman iwiseman@bibble.co.nz"]
[dependencies]
rand = "0.3.12"
phrases = { path = "../Phrases" }
Testing
Rush comes with it's own assert library marked by #[test] e.g. for the above
#[test]
fn english_greeting_correct()
{
assert_eq!("hello", greetings::english::hello());
}
Documentation
rustdoc is used to generate.
//! This module comment
//! and this
//! #Examples are compiled with back ticks
//! ```
//! let username = "John";
//! println!("{}", english::hello());
//! ```
/// This is for code
/// In this case, it's our `hello()` function.
pub fn hello() -> String {" hello".to_string() }
Installing
Do not use apt as it does not set rustup correctly and then vscode extension will not work with "Couldn't start client Rust Language Server"
apt-get install curl build-essential make gcc -y
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
VS Code setup
I ended up installing. Still cannot find a in built package manager for cargo
Date Handling
Found the on twitter (not all bad) @orhanbalci Type conversion and dates are the worst in most languages so thought I would keep this.
fn main() {
// you can use chrono crate for date time operations.
// chrono crate is more capable than standard library std::time module
use chrono::prelude::*;
use chrono::Duration;
// you can retrieve current date in Utc timezone as follows
let current_date = Utc::today();
println!("Utc current date: {}", current_date);
// you can retrieve current date in local timezone as follows
let local_current_date = Local::today();
println!("Local current date: {}", local_current_date);
// you can retrieve current time in UTC as follows
let current_time_utc = Utc::now();
println!( "Utc current time: {}", current_time_utc);
// you can retrieve current time in local time zone as follows
let current_time_local = Local::now();
println!("Local current time: {}", current_time_local);
// you can add some duration to a chrono::Date
// succ method gets succeeding date
let today = Utc::today();
let tomorrow = today + Duration::days(1);
assert_eq!(today.succ(), tomorrow);
// you can subtract some duration from a chrono
// pred method gets previous date
let today = Utc::today();
let yesterday = today - Duration::days(1);
assert_eq!(today.pred(), yesterday);
// you can get UNIX timestamp (epoch) value for a datetime using
// timestamp method of chrono::offset::TimeZone trait.
// since timestamps have numeric representation, they are easy to store in db
// and send through network. You can aldo prefer this notation in your APIs
let dt = Utc.ymd(1970, 1, 1).and_hms_milli(0, 0, 1, 0);
assert_eq!(dt.timestamp(), 1);
//you can also get timestamp value of a datetime in milliseconds
let dt = Utc.ymd(1970, 1, 1).and_hms_milli(0, 0, 1, 500);
assert_eq!(dt.timestamp_millis(), 1500);
//you can convert create a chrono::DateTime from a timestamp seconds
let timestamp = 15;
let datetime = Utc.timestamp(timestamp, 0);
assert_eq!(datetime.timestamp(), 15);
//you can get difference of two date times as follows
let first = Utc.ymd(1970, 1, 1).and_hms_milli(0, 0, 1, 0);
let second = Utc.ymd(1970, 1, 1).and_hms_milli(0, 0, 2, 0);
let difference: Duration = second.signed_duration_since(first);
assert_eq!(difference, Duration::seconds(1));
// you can also add and subtract duration from a DateTime struct
let now = Utc::now();
let yesterday_at_the_same_time = now - Duration::days(1);
println!("Yesterday at the same time: {}", yesterday_at_the_same_time);
//you can compare durations
assert_eq! (Duration::days(1), Duration::hours(24));
//you can format your DateTime struct using strftime formatting options
let now = Utc::now();
println!("{}", now. format( "%d.%m.%Y %H:%M"));
//you can parse DateTime from a formatted string as below
let some_time = NaiveDateTime::parse_from_str("01.10.2021 14:21", "%d.%m.%Y %H:%M").unwrap( );
assert_eq!(some_time, NaiveDate::from_ymd(2021,10,1).and_hms(14,21,0));
}
Type State Pattern
In some cases we only want to be able to do some functions on a struct based on the state of the struct. In rust we could create a field in the struct called state and maintain which functions are allowed based on the current state. However rust provides a better approach referred to as the type state pattern. For example if we wanted make a password manager structure we could do this.
#![allow(unused)]
use std::collections::HashMap;
use std::marker::PhantomData;
struct Locked;
struct Unlocked;
// PasswordManager<Locked> != PasswordManager<Unlocked>
struct PasswordManager<State = Locked> {
master_pass: String,
passwords: HashMap<String, String>,
state: PhantomData<State>,
}
// Locked Functions
impl PasswordManager<Locked> {
pub fn unlock(self, master_pass: String) -> PasswordManager<Unlocked> {
PasswordManager {
master_pass: self.master_pass,
passwords: self.passwords,
state: PhantomData,
}
}
}
// Unlocked Functions
impl PasswordManager<Unlocked> {
pub fn lock(self) -> PasswordManager<Locked> {
PasswordManager {
master_pass: self.master_pass,
passwords: self.passwords,
state: PhantomData,
}
}
pub fn list_passwords(&self) -> &HashMap<String, String> {
&self.passwords
}
pub fn add_password(&mut self, username: String, password: String) {
self.passwords.insert(username, password);
}
}
// Generic Functions
impl<State> PasswordManager<State> {
pub fn encryption(&self) -> String {
todo!()
}
pub fn version(&self) -> String {
todo!()
}
}
// Constructor
impl PasswordManager {
pub fn new(master_pass: String) -> Self {
PasswordManager {
master_pass,
passwords: Default::default(),
state: PhantomData,
}
}
}
fn main() {
let mut manager = PasswordManager::new("password123".to_owned());
let manager = manager.unlock("password123".to_owned());
manager.list_passwords();
manager.lock();
}
The benefits to doing this are
- Only functions allowed are displayed in the IDE. E.g. a locked password manager cannot see the functions for an unlocked password manager
- The state PhantomData<State> is compiled out and reduces memory footprint