Udemy - Rust

07 User Input

main.rs

use std::io;
fn main() {
let mut input = String::new();
println!("Say something");
match io::stdin().read_line(&mut input) {
Ok(_) => {
println!("You said {}", input)
},
Err(e) => {
println!("Something went wrong {}", e)
}
}
}

08 Comments

Line comments

// This is a line comment

Multi line comments are allowed but rarely used

/* This is not
very common
*/

Doc comments

/// This is mainly used to document functionality

Heading

//! # Main heading

Code

//! ```
//! fn main() {...}
//! ```

09 println!

println!("Hello world!");

Formatting

println!("My name is {} and I'm {} years old", "Alex", 29);

Expressions

println!("a + b = {}", 3 + 6)

Positional arguments

println!("{0} has a {2} and {0} has a {1}", "Alex", "cat", "dog");

Named arguments

println!("{name} {surname}", surname="Smith", name="Alex");

Printing traits

print!("binary: {:b}, Hex: {:x}, Octal: {:o}", 5, 5, 5);

Debug

println!("Array {:?}", [1, 2, 3]);

10. Language basics - Overview

  • Variables
  • Scalar data types
  • Strings
  • Constants
  • Operators
  • Functions

11. Variables

let name = "Michael"
let age = 32

Rust is a strongly typed language

Variable type is optional if it can be inferred

Type can be specified explicitly

let amount: i64 = 8473926758472
let amount = 8473926758472 // error

Names can contain letters, numbers, and underscores

Must start with a letter or underscore

Immutable by default

let length = 34;
length = 35; // error

Can be declared mutable

let mut length = 34;
length = 35;

Shadowing is allowed

let color = "blue";
let color = "red";
println!("Color is {}", color); // Color is red

Declaring multiple variables simultaneously

let (a, b, c) = (2, 3, 4);

12. Scalar data types

Integer

SizeSignedUnsigned
8biti8u8
16biti16u16
32biti32u32
64biti64u64
128biti128u128
archisizeusize

Float

SizeFloat
32bitf32
64bitf64

Type casting

let pi: f32 = 4; // mismatched types error

Number separator

let million = 1_000_000;
let random = 3_836.45_346_546;

Boolean

let is_day = true;
let is_night = false;

Character

let char1 = 'A';
let smiley_face = '\u{1F601}';

13. String

String slices

let cat: &str = "Fluffy";
let cat: &'static str = "Fluffy";

String slices are immutable

String objects

let dog = String::new();
let mut dog = String::from("Max");

format!

format!("Hi {} how are you", "Mark");

String method

len

println!("{}", dog.len());

push & push_str

dog.push(' '); // push one charecter.
dog.push_str("the dog"); // push whole string.
println("{}", dog); // Max the dog

replace

let new_dog = dog.replace("the", "is_my");
println!("{}", new_dog);

14. Constant

Values that cannot be changed

const URL: &str = "google.com";

Uppercase by convention

Data type is mandatory

Shadowing is not premitted

Global or local scope

15. Operator

Arithmetic operators

OperatorNameExample
+addition10 + 3 = 3
-subtraction10 - 3 = 7
*multiplication10 * 3 = 30
/division10 / 3 = 3
%modules10 % 3 = 1

Note: Increment(++) and decrement(--) are not supported

Relational operators

OperatorNameExample
>Greater than10 > 3 = true
>=Greater than or equal to"A" >= "a" = false
<Lesser than10 < 3 = false
<=Lesser than or equal totrue <= false = false
==Equal3.0 == 3.1 = true
!=Not equal'c' != 'C' == true

Logical operators

OperatorNameExample
&&ANDtrue && true = true
||ORtrue || false = true
!NOT!true = false

Bitwise operators

OperatorNameExample
&Bitwise AND10 > 3 = true
|Bitwise OR"A" >= "a" = false
^Bitwise XOR10 < 3 = false
!NOTtrue <= false = false
<<Left shift10 << 1 = 20
>>Right shift10 >> 1 = 5

15. Functions

fn say_hi() {
println!("Hello there!");
}
fn main() {
say_hi();
}
fn main() {
for i in 1..6 {
say_hi();
}
}

Function parametes

Pass by value

fn main() {
let mut name = "John";
say_hello(name);
println!("{}", name);
}
fn say_hello(name: &str) {
println!("Hello {}", name);
}

Pass by reference

fn main() {
let mut name = "John";
say_hello(&mut name);
println("{}", name);
}
fn say_hello(name: &mut &str) {
*name = "Alex";
println!("Hello {}", name)
}

Return values

fn main() {
let mut name = "John";
let greeting = say_hello(&mut name);
println("{}", greeting);
}
fn say_hello(name: &mut &str) -> String {
let greeting = format!("Hello {}", name);
return greeting;
}

17. Modules Overview

  • Modules
  • Crates

18. Modules

Create a module

pub mod mod_name {
pub fn do_something () {
...
}
}
mod mod_name;
fn main() {
mod_mane::do_something();
}

Nested modules

pub mod mod_name {
pub mod submod {
fn fun_submodule() {
...
}
}
}
mod mod_name;
fn main() {
mod_name::submod::fun_submodule();
}

19. Crates

Multiple modules are grouped into a crate

Two types

  • binary crates
  • library crates

Cargo is used to manage crates

External crates are imported into the project must be added to the toml file.

use crate::archive::arch::arch_file as arc;
use rand::Rng;
mod archive;
fn main() {
arc("somefile.txt");
let mut rng = rand::thread_rng();
let a: i32 = rng.gen();
println!("{}", a);
}
// archive.rs
pub mod arch {
pub fn arch_file(name: &str) {
println!("Archiving file {}", name);
}
}

20. Random

Generate an integer

use rand::Rng;
let mut rng = rand::thread_rng();
rng.gen();

Bounded generation

rng.gen_range(0, 10);
// 生成0~10的随机浮点数
rng.gen_range(0.0, 10.0)

String generation

use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric
fn main() {
let rand_string: String = thread_rng()
.sample_iter(&Alphanumeric)
.take(30)
.collect();
println!("{}", rand_string);
}

21. Data types

  • Arrays
  • Vectors
  • Slices
  • Tuples
  • Structures
  • Enums
  • Generics

22. Arrays

A collection of values of the same type

let primes = [2, 3, 5, 7, 11];
let doubles: [f64; 4] = [2.0, 4.0, 6.0, 8.0];
  • Static - cannot be resized
  • Element values can be modified but not deleted
  • Indexed

Create array with default values

let mut numbers = [0;15];
const DEFAULT: i32 = 3;
let numbers = [DEFAULT;15];

Updating elements

numbers[3] = 5;

Using an iterator

for number in numbers.iter() {
println!('{}', number + 3);
}

23. Vectors

Arrays of variables size

let mut primes: Vec<i32> = Vec::new();
let mut primes = vec![2, 3, 5];

Adding elements

primes.push(7);

Removing elements

primes.remove(2);

Create vectors with default values

let mut numbers = vec![2;10];
const DEFAULT: i32 = 6;
let mut numbers = [DEFAULT;8];

24. Slices

A slice is a pointer to a block of memory

let numbers = [1, 2, 3, 4, 5];
let slice = &numbers[1..4];
  • Size is determined at runtime
  • Can be used on arrays, vectors and things
  • Indexed

Mutable slices allow us to change values

25. Tuples

A collection of values of various types

let person = {"John", 27, true};
let person: (&str, i32, bool) = ("John", 27, true);
  • Static - cannot be resized
  • Element values can be updated
  • Indexed
  • Limited to 12 elements

Accessing elements

println!("Name: {}", person.0);

Updating elements

person.0 = "Jack";

Destructuring a tuple

let (name, age, employed) = person;

number of variables must correspond to number of elements

26. Sturctures

A collections of key-value pairs

struct Employee {
name: String,
company: String,
age: u32
}
let emp1 = Employee {
name: String::from("John"),
company: String::from("Google"),
age: 35
}
println("{}", emp1.name);

Adding methods to a structure

impl Employee {
fn fn_detail(&self) -> String {
format!("name: {}, age: {}, company: {}", &self.name, &self.age, &self.company)
}
}

A structure can have static methods

impl Employee {
fn static_fn_detail() -> String {
String::from("Details of a person")
}
}

27. Enums

A collection of values

enum Color {
Red,
Green,
Blue
}
let my_color = Color::Red;
let my_color = Red;

We can add data types to enum elements

enum Person {
Name(String),
Surname(String),
Age(u32)
}

28. Generics

Allows us to have variable data types

struct Point<T> {
x: T,
y: T
}
let p1: Point<i32> = Point {x: 6, y: 8};
let p2: Point<f64> = Point {x: 3.25, y: 8.43};
struct Point<T, V> {
x: T,
y: V
}
let p3: Point<i32, f64> = Point{x: 5, y: 3.24};

29. Control structures

  • If statement
  • Match statement
  • Pattern matching
  • For loop
  • While loop

30. If statement

image-20220626160711118

if logical_expression {
functionality for true
}
if logical_expression {
functionality for true
} else {
functionality for false
}
if expression1 {
functionality for expression1 true
} else if expression2 {
functionality for expression1 false and expression2 true
} else {
functionality for both expressions false
}

If statement can return a result

let res = if expr1 {
result for true
} else {
result for false
}

31 Match

Similar to when or switch in other languages

match expression {
expr1 => { ... }
expr2 => { ... }
_ => { ... }
}
  • Can return a result
  • Ranges are allowed
fn country(code: i32) {
let country = match code {
44 => "UK",
34 => "Spain",
1...999 => "unknown",
_ => "invalid"
}
println!("Country is {}", country);
}

32 Pattern matching

match expression {
expr1 => { ... }
expr2 => { ... }
_ => { ... }
}
  • Multiple values 1 | 2
  • Ranges 1..=5
  • Conditions x if a > b
  • Tuple matching
fn get_oranges(amount: i32) -> &'static str {
return match amount {
0 => "no",
1 | 2 => "one or two",
3..=7 => "a few",
_ if (amount % 2 == 0) => "an even amount of",
_ => "lots of"
}
}

33 For Loop

Loop through a collection or range, execute code for each element

for element in collection {
functionality
}
fn main() {
for i in 1..11 {
println!("{0} * {0} = {1}", i, i * i);
}
}

Continue will skip a step

Break will stop the loop

34. While Loop

Loop as long as a condition is true

while condition {
...
}

Continue skips a step

Break stops the loop

fn get_squares(limit: i32) {
let mut x = 1;
while x * x < limit {
println!("{0} * {0} = {1}", x, x * x);
x += 1;
}
}
fn get_cubes(limit: i32) {
let mut x = 1;
loop {
println!("{0} * {0} * {0} = {1}", x, x * x * x);
x += 1
if x * x * x > limit {
break
}
}
}

35 Functions Overview

  • Functions and scope
  • Closures
  • Higher Order Functions
  • Macros

36. Functions and scope

Functions

fn main() {
say_hi();
}
fn say_hi() {
println!("Hello there!");
}
fn main() {
let mut name = "John";
say_hi(&mut name);
print!("The new name is {}", name);
}
fn say_hi(name: &mut &str) {
*name = "Alex";
println!("Hello {}!", name);
}

Scope

No memory leaks - no need to manually deallocate variables

{
let a = 3;
}
println("a = {}", a) // error

Global variables can be declared but they are unsafe

let a = 3;
fn main() {
unsafe { println!("{}", a); }
}

37. Closures

A function within a function

An anonymous function, lambda expression

|a: i32, b: i32| println("{}", a + b);
|a: i32, b: i32| -> i32 {a + b};

A function can assigned to a variable

let sum = |a: i32, b: i32| -> i32 {a + b};
sum(2, 3);

A clujure can be genetic

let gen = |x| { println!("received {}"), x };
gen(3);

38. HOFs

Functions that take another function as a parameter

fn apply (f: fn(i32) -> i32, a: i32) {
}
apply(|x| -> x + 1, a);

39. Macros

Write code that writes code - meta programming

Match an expression and perform some operation

Code is expanded and compiled

macro_rules! my_macro {
(match) => ( code to run )
}
my_macro!
println!("This is an {} macro", "awesome");

We can match multiple expressions

macro_rules! my_macro {
(match1) => ( code to run )
(match2) => ( code to run )
}

Designators

  • expr
  • ident
  • block
  • stmt
  • path
  • meta
  • ty
  • tt

example

macro_rules! name {
($name: expr) => { println!("Hey {}", $name) }
}
macro_rules! name2 {
($($name: expr), *) => ( $(println!("Hey {}", $name);)* );
}
macro_rules! xy {
(x => $e: expr) => (println!("X is {}", $e));
(y => $e: expr) => (println!("Y is {}", $e));
}
macro_rules! build_fn {
($fn_name: ident) => {
fn $fn_name() {
println!("{:?} was called", stringify!($fn_name))
}
}
}
fn main() {
name!("John");
name2!("Alex", "Mary", "Carol");
xy!(x => 5);
xy!(y => 3 * 9);
build_fn!(hey);
hey();
}

40. Trait Overview

  • Traits
  • Generics
  • dyn
  • Operator overloading
  • Static dispatch
  • Dynamic dispatch

41. Traits

Similar to an interface or abstract class

Add a definition to a structure

trait Name {
fn must_implement(&self) -> i32;
fn dn_action(&self) {...}
fn do_non_instance_action() {...}
}

Can have definition only or default implementation

Can have instance and non-instance action

implement a trait

impl Name for Person {
fn must_implement(&self) -> { 42 }
fn new(name: &str) -> Person {
Person(name: name)
}
}

Can provide a constructor

trait Name {
fn new(name: &str) -> Self;
}
let john = Person::new("John");

example

struct RustDev {
awesome: bool
}
struct JavaDev {
awesome: bool
}
trait Developer {
fn new(awesome: bool) -> Self;
fn language(&self) -> &str;
fn say_hello(&self) { println!("Hello world!") }
}
impl Developer for RustDev {
fn new(awesome: bool) -> Self {
RustDev { awesome: awesome }
}
fn language(&self) -> &str {
"Rust"
}
fn say_hello(&self) {
println!("println!(\"Hello world!\");");
}
}
impl Developer for JavaDev {
fn new(awesome: bool) -> Self {
JavaDev { awesome: awesome }
}
fn language(&self) -> &str {
"Java 1.8"
}
fn say_hello(&self) {
println!("System.out.println(\"Hello world!\");");
}
}
fn main() {
let r = RustDev::new(true);
let j = JavaDev::new(false);
println!("{}", r.language());
r.say_hello();
}

42. Generics

Generics can be limited by traits

fn color<T: Colorable> (a: T) {
// ...
}

Demo

trait Bark {
fn bark(&self) -> String;
}
struct Dog {
species: &'static str
}
struct Cat {
color: &'static str
}
impl Bark for Dog {
fn bark(&self) -> String {
return format!("{} barking", self.species);
}
}
fn bark_it<T: Bark>(b: T) {
println!("{}", b.bark())
}
fn main() {
let dog = Dog { species: "retriever" };
let cat = Cat { color: "black" };
bark_it(dog);
}

43. Returning traits

The compiler needs to know the space required for a function return type.

A workaround is to return a box with a dyn trait.

fn get_animal() -> Box<dyn Animal> {
...
}

dyn is a new addition to the language old code might not have it.

44. Adding traits to existing structures

We can add a trait to a structure we didn't create

impl My_Trait for Vec<i32> {
...
}
#[allow(unused_variables)]
#[allow(unused_assignments)]
trait Summable<T> {
fn sum(&self) -> T;
}
impl Summable<i32> for Vec<i32> {
fn sum(&self) -> i32 {
let mut sum: i32 = 0;
for i in self {
sum += *i;
}
sum
}
}
fn main() {
let a = vec![1, 2, 3, 4, 5];
println!("sum = {}", a.sum())
}

45. Operator overloading

We can implement standard operators for our custom structs

use std::ops::Add;
struct Custom {
// ...
}
impl Add for Custom {
type Output = Custom;
fn add(self: Custom, other: Custom) -> Custom {
// ...
}
}

Demo

use std::ops::Add;
#[allow(unused_variables)]
#[allow(unused_assignments)]
#[derive(Debug)]
struct Point {
x: f64,
y: f64
}
impl Add for Point {
type Output = Point;
fn add(self, other:Self) -> Self::Output {
Point {
x: self.x + other.x,
y: self.y + other.y
}
}
}
fn main() {
let p1 = Point { x: 1.3, y: 4.6 };
let p2 = Point { x: 3.7, y: 1.4 };
let p3 = p1 + p2;
println!("{:?}", p3)
}

46. Static dispatch

A generic trait will be converted to the required type at compile time

Monomorphization

  • converting to one form

Demo

#[allow(unused_variables)]
#[allow(unused_assignments)]
trait Duplicateable {
fn dupl(&self) -> String;
}
impl Duplicateable for String {
fn dupl(&self) -> String {
format!("{0}{0}", *self)
}
}
impl Duplicateable for i32 {
fn dupl(&self) -> String {
format!("{}", *self * 2)
}
}
fn duplicate<T: Duplicateable> (x: T) {
println!("{}", x.dupl());
}
fn main() {
let a = 42;
let b = "Hi John ".to_string();
duplicate(a);
duplicate(b);
}

47. Dynamic dispatch

A generic trait will be converted to the required type at run time

Demo

#[allow(unused_variables)]
#[allow(unused_assignments)]
trait Duplicateable {
fn dupl(&self) -> String;
}
impl Duplicateable for String {
fn dupl(&self) -> String {
format!("{0}{0}", *self)
}
}
impl Duplicateable for i32 {
fn dupl(&self) -> String {
format!("{}", *self * 2)
}
}
fn duplicate(x: &dyn Duplicateable) {
println!("{}", x.dupl())
}
fn main() {
let a = 42;
let b = "Hi John ".to_string();
duplicate(&a);
duplicate(&b);
}

48. Memory management Overview

  • Ownership
  • Borrowing
  • Lifttimes
  • Reference counted variables

49. Ownership

Only one variable can own a piece of memory

For primitive types, copying data is cheap

For complex types, ownership is transferred.

Demo

#[allow(unused_variables)]
#[allow(unused_assignments)]
fn main() {
let i = 5;
let j = i;
println!("{}", j);
println!("{}", i);
let v = vec![1, 2, 3, 4, 5];
// let w = v;
// println!("{:?}", w);
// println!("{:?}", v);
let foo = |v: Vec<i32>| -> Vec<i32> {
println!("Vector used in foo");
v
};
let v = foo(v);
println!("{:?}", v);
}

50. Borrowing

Only one variable can own a piece of memory

Variables can borrow ownership to other pieces of memory

let a = 6;
let b = &a;
println("{}", *b);

Demo

#[allow(unused_variables)]
#[allow(unused_assignments)]
fn main() {
let mut a = 6;
{
let b = &mut a;
println!("{}", *b);
*b += 2;
}
println!("{}", a);
let mut v = vec![1, 2, 3, 4, 5];
for i in &v {
println!("{}", i);
v.push(6);
}
}

51. Lifetime

An indication of how long an object will live

Rust prevents parts to objects outliving the object

struct Object<'lifetime> {
field: &'lifetime str
}

Lifetime elision - compiler builds lifetimes for us when evident.

Demo

#[allow(unused_variables)]
#[allow(unused_assignments)]
#[derive(Debug)]
struct Person {
name: String
}
#[derive(Debug)]
struct Dog<'l> {
name: String,
owner: &'l Person
}
impl Person {
fn get_name(&self) -> &String {
&self.name
}
}
fn main() {
println!("{}", get_str());
let p1 = Person { name: String::from("John") };
let d1 = Dog { name: String::from("Max"), owner: &p1 };
println!("{:?}", d1);
let mut a: &String;
{
let p2 = Person { name: String::from("Mary") };
a = p2.get_name();
}
println!("{}", a);
}
fn get_str() -> &'static str {
"Hello"
}