Vecs and Slices

Viewing Lists Conveniently

let mut numbers = vec![2, 5, 16];
// let mut numbers = Vec::new();

numbers.push(8);

assert!(vec.len() == 4);

assert!(vec.pop() == Some(8));

for number in &numbers {
    println("in vec: {}", number);
}
ptr
len 4
capacity 8

Vec

...

fn print_buffer(buffer: &Vec<u8>) {
    for num in buffer {
    	println!("{}", num);
    }
}

fn main() {
    let buffer: Vec<u8> = vec![2, 4, 8, 16, 32, 64];
    
    print_buffer(&buffer);
}
fn print_buffer(buffer: &[u8]) {
    for num in buffer {
    	println!("{}", num);
    }
}

fn main() {
    let buffer: Vec<u8> = vec![2, 4, 8, 16, 32, 64];
    
    print_buffer(&buffer);
}

[T]

let buffer = vec![1, 2, 3, 4, 5, 6];

let buffer_slice = &buffer[1..4];
println!("{:?}", buffer_slice);
&mut [u8]
&mut Vec<u8>

vs

When would you use one vs the other?

[u8; 8]

Strings

String

pub fn from_utf8(vec: Vec<u8, Global>) 
    -> Result<String, FromUtf8Error>
ptr
len 4
capacity 8

String

...

String

str

Vec<T>

[T]

let string_literal: &str = "foobar";
fn print_string(string: &String) {
    println!("{}", string);
}

fn main() {
    let my_string = String::from("foobar");
    
    print_string(&my_string);
}
fn print_string(string: &str) {
    println!("{}", string);
}

fn main() {
    let my_string = String::from("foobar");
    
    print_string(&my_string);
}
fn print_string(string: &str) {
    println!("{}", string);
}

fn main() {
    let my_string = String::from("foobar");
    
    print_string(&my_string[1..4]);
}
let string = "foo bar baz";

for character in string.chars() {
    println!("char: {}", character);
}

Lifetimes

fn passthrough_reference(string: &str) -> &str {
    string
}

fn main() {

    let string = String::from("foobar");
    
    let string_ref = passthrough_reference(&string);
    
    std::mem::drop(string);
    println!("{}", string_ref);

}
error[E0505]: cannot move out of `string` because it is borrowed
  --> src/main.rs:11:20
   |
9  |     let string_ref = passthrough_reference(&string);
   |                                            ------- borrow of `string`
                                                        occurs here
10 |     
11 |     std::mem::drop(string);
   |                    ^^^^^^ move out of `string` occurs here
12 |     println!("{}", string_ref);
   |                    ---------- borrow later used here
fn passthrough_reference(string: &str) -> &str {
    string
}
fn passthrough_reference<'a>(string: &'a str) -> &'a str {
    string
}
fn passthrough_reference<'a>(string: &'a str) -> &'a str {
    string
}

fn main() {

    let string = String::from("foobar");
    
    let string_ref = passthrough_reference(&string);
    
    std::mem::drop(string);
    println!("{}", string_ref);

}
fn passthrough_reference(
    string_a: &str,
    string_b: &str,
) -> &str {
    string_a
}
fn passthrough_reference(
    string_a: &str,
    string_b: &str,
) -> &str {
    string_a
}
error[E0106]: missing lifetime specifier
 --> src/lib.rs:4:6
  |
2 |     string_a: &str,
  |               ----
3 |     string_b: &str,
  |               ----
4 | ) -> &str {
  |      ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed 
          value, but the signature does not say whether it 
          is borrowed from `string_a` or `string_b`
help: consider introducing a named lifetime parameter
  |
1 | fn passthrough_reference<'a>(
2 |     string_a: &'a str,
3 |     string_b: &'a str,
4 | ) -> &'a str {
  |

cargo run

fn passthrough_reference<'a>(
    string_a: &'a str,
    string_b: &'a str,
) -> &'a str {
    string_a
}
Finished dev [unoptimized + debuginfo] target(s) in 0.81s

cargo run

let string_literal: &'static str = "foobar";

Traits

Defining Shared Behavior

/// A trait which provides the functionality of printing
/// itself to the stdout.
trait Printable {

    /// When called, will print the object to stdout.
    fn print(&self);
    
}
struct MyStruct {
    number: u32,
}

impl Printable for MyStruct {

    fn print(&self) {
        println!(
            "MyStruct {{ number: {} }}", 
            self.number,
        );
    }
    
}

Rust has a Defa  ult trait.

It has a function that should return the default value of a data type.

Implement Def  ault for BankAccount so that the balance is initialized to 0.

struct BankAccount {
    balance: i64,
}

Default

Default

struct BankAccount {
    balance: i64,
}

impl Default for BankAccount {
    fn default() -> Self {
        BankAccount {
            balance: 0,
        }
    }
}
BankAccount::default()
struct Counter {
    count: u32,
}

impl Counter {
    
    fn increment(&mut self, num: u32) {
        self.count += num;
    }
    
}
  1. Implement a trait from this crate           for a struct from this crate
  2. Implement a trait from another crate for a struct from this crate
  3. Implement a trait from this crate         for a struct from another crate
  4. Implement a trait from another crate for a struct from another crate

Consider these cases, should the Rust compiler allow them?

struct MyStruct {
    [...]
}

impl MyTrait for MyStruct {
    fn my_trait_method(&self) {
        [...]
    }
}

file1.rs

use file1::MyStruct;

fn a_func() {

    let my_instance = MyStruct {
        [...]
    };

    my_instance.my_trait_method();

}

file2.rs

Rule:

Traits must be in scope in order to call methods on them!

use file1::MyTrait;

Generics

Powerful Generic Data Structures

enum Option<T> {
    Some(T),
    None,
}
fn main() {
    let my_option = Some(5);
    
    match my_option {
        Some(num) => println!("Option contained {}", num),
        None => println!("Option was None"),
    }
}

There is no Either type in the standard library.

An Either type might contain either of two individual types, Left or Right. These two types might be different.

 

How might we declare such a type using an Enum with type parameters?

enum Either<A, B> {
    Left(A),
    Right(B),
}
struct Addable<T>(T);

impl<T> Addable<T> {

    fn add(&self, other: &Self) -> Self {
        Self(self.0 + other.0)
    }
    
}

fn main() {

    let a = Addable(1);
    let b = Addable(2);
    
    let c = a.add(&b);

}
error[E0369]: cannot add `T` to `T`
 --> src/main.rs:6:21
  |
6 |         Self(self.0 + other.0)
  |              ------ ^ ------- T
  |              |
  |              T
  |

cargo run

Will this code compile?

struct Addable<T>(T);

impl<T> Addable<T> {

    fn add(&self, other: &Self) -> Self {
        Self(self.0 + other.0)
    }
    
}
struct Addable<T>(T);

impl<T> Addable<T> {

    fn add(&self, other: &Self) -> Self {
        Self(self.0 + other.0)
    }
    
}

cargo run

error[E0369]: cannot add `T` to `T`
 --> src/lib.rs:6:21
  |
6 |         Self(self.0 + other.0)
  |              ------ ^ ------- T
  |              |
  |              T
  |
help: consider restricting type parameter `T`
  |
3 | impl<T: std::ops::Add<Output = T>> Addable<T> {
  |       ^^^^^^^^^^^^^^^^^^^^^^^^^^^
struct Addable<T>(T);

impl<T: std::ops::Add<Output = T>> Addable<T> {

    fn add(&self, other: &Self) -> Self {
        Self(self.0 + other.0)
    }
    
}

cargo run

error[E0507]: cannot move out of `self.0` which is 
              behind a shared reference
 --> src/lib.rs:6:14
  |
6 |         Self(self.0 + other.0)
  |              ^^^^^^ move occurs because `self.0` 
                        has type `T`, which does not 
                        implement the `Copy` trait
struct Addable<T>(T);

impl<T: std::ops::Add<Output = T> + Copy> Addable<T> {

    fn add(&self, other: &Self) -> Self {
        Self(self.0 + other.0)
    }
    
}
struct Addable<T>(T);

impl<T: std::ops::Add<Output = T> + Copy> Addable<T> {

    fn add(&self, other: &Self) -> Self {
        Self(self.0 + other.0)
    }
    
}

fn main() {

    let a = Addable(1);
    let b = Addable(2);
    
    let c = a.add(&b);
    println!("{}", c.0);
}

cargo run

$ cargo run
3

Rust vs OOP

Encapsulation

Polymorphism

Encapsulation

struct MyStruct {
    private_state: u32,
    pub public_state: u32,
}

impl MyStruct {
    
    fn private_method(&self) {
        [...]
    }
    
    pub fn public_method(&self) {
        [...]
    }
    
}

Encapsulation

struct MyStruct {
    private_state: MyInnerStruct,
    pub public_state: u32,
}

impl MyStruct {

    fn private_method(&self) {
        [...]
    }
    
    pub fn public_method(&self) {
        self.private_state.do_something()
    }
    
}

Encapsulation

Polymorphism

Polymorphism

Traits!

Traits:

  • A lot like interfaces
    • A type can implement several
    • No inheritance
  • Traits can be ad-hoc implemented

i32

f32

trait Delayable

BigInt

trait Delayable {
    fn sleep(&self);
}

impl Delayable for i32 {
    fn sleep(&self) {
        [...]
    }
}

impl Delayable for f32 {
    fn sleep(&self) {
        [...]
    }
}
  • Avoid diamond problem
  • Uniform representation
  • Ability to ad-hoc implement traits on existing types

Dynamic vs Static Dispatch

  • many variations of the machine code is generated
  • one for each type used with the algorithm
  • type specific behavior compiled in
  • single variation of machine code is generated
  • one supports all types aaaaaa
  • type specific behavior handled with call to vtable

Static Dispatch

Dynamic Dispatch

use std::fmt::Debug;

fn print_item<T: Debug>(item: &T) {
    println!("{:?}", item);
}
use std::fmt::Debug;

fn print_item(item: &impl Debug) {
    println!("{:?}", item);
}
use std::fmt::Debug;

fn print_items<T1: Debug, T2: Debug>(
    item1: &T1, 
    item2: &T2,
) {
    println!("{:?} {:?}", item1, item2);
}
use std::fmt::Debug;

fn print_items(
    item1: &dyn Debug, 
    item2: &dyn Debug,
) {
    println!("{:?} {:?}", item1, item2);
}
use std::fmt::Debug;

fn print_items(
    item1: &dyn Debug, 
    item2: &dyn Debug,
) {
    println!("{:?} {:?}", item1, item2);
}

More Traits

trait Iterator {
    type Item;
    
    fn next(&mut self) -> Option<Self::Item>;
    
    [...]
}
struct CountIterator {
   current: u32,
   to: u32,
}

impl Iterator for CountInterator {
    type Item = u32;
    
    fn next(&mut self) -> Option<Self::Item> {
        if self.current < self.to {
            self.current += 1;
            Some(self.current)
        } else {
            None
        }
    }
}
trait Iterator {
    type Item;
    
    fn next(&mut self) -> Option<Self::Item>;
    
    fn size_hint(&self) -> (usize, Option<usize>) { ... }
    fn count(self) -> usize { ... }
    fn last(self) -> Option<Self::Item> { ... }
    fn advance_by(&mut self, n: usize) -> Result<(), usize> { ... }
    fn nth(&mut self, n: usize) -> Option<Self::Item> { ... }
    [...]
}
pub trait Add<Rhs = Self> {
    type Output;
    fn add(self, rhs: Rhs) -> Self::Output;
}
  • Type Parameters: Input types
  • Associated Types: Output Types
trait CakeMaker<Topping> {
    type CakeType;
    fn make() -> CakeType;
}

// Cake Bases
struct Small;
struct Flat;

// Toppings
struct Glaze;
struct Nothing;

// Cakes
#[derive(Debug)]
struct Cupcake;

#[derive(Debug)]
struct Cookie;

#[derive(Debug)]
struct CookieWithGlaze;

impl CakeMaker<Glaze> for Small {
    type CakeType = Cupcake;
    fn make()
}
impl CakeMaker<Nothing> for Flat {
    type CakeType = Cookie;
}
impl CakeMaker<Glaze> for Flat {
    type CakeType = Cupcake;
}

Type Parameters

Closures

fn main() {

    let string = String::from("foobar");

    let print_string = || {
        println!("{}", string);
    };

    print_string();
    print_string();

}
foobar
foobar

cargo run

fn make_print_string_closure(string: String) -> impl Fn() {
    || {
        println!("{}", string);
    }
}

fn main() {

    let string = String::from("abc");
    
    let closure = make_print_string_closure(string);

    closure();
    closure();

}
error[E0373]: closure may outlive the current function, but it 
              borrows `string`, which is owned by the current 
              function
 --> src/main.rs:2:5
  |
2 |     || {
  |     ^^ may outlive borrowed value `string`
3 |         println!("{}", string);
  |                        ------ `string` is borrowed here
  |
note: closure is returned here
 --> src/main.rs:1:49
  |
1 | fn make_print_string_closure(string: String) -> impl Fn() {
  |                                                 ^^^^^^^^^
help: to force the closure to take ownership of `string` (and any 
      other referenced variables), use the `move` keyword
  |
2 |     move || {
  |     ^^^^^^^
fn make_print_string_closure(string: String) -> impl Fn() {
    move || {
        println!("{}", string);
    }
}

fn main() {

    let string = String::from("abc");
    
    let closure = make_print_string_closure(string);

    closure();
    closure();

}

cargo run

foobar
foobar

Fn

FnMut

FnOnce

  • owns or borrows its environment immutably
  • may be called multiple times
  • borrows its environment mutably
  • may be called multiple times
  • owns its environment aaaaaaa
  • can be called only once
  • consumes its envionment on invocation

Iterators

for n in 0..10 {
    println!("{}", n);
}

0..10

std::ops::Range {
    start: 0,
    end: 10,
}
std::ops::Range {
    start: 0,
    end: 10,
}
let sum: u32 = (1..10)
    .map(|i| i * 2)
    .sum();
    
println!("{}", sum);

cargo run

90
let sum: u32 = (1..10)
    .map(|i| i * 2) // <-- what type?
    .sum();
    
println!("{}", sum);
let array = [1, 2, 3, 4, 5];

for (idx, item) in array.iter().enumerate() {
    println!("{}: {}", idx, item);
}

Thread Safety

Tread Safely

Example of threading bug prevented by Rust

unsafe auto trait Send {}

unsafe auto trait Sync {}

auto traits

Must:

  • Be a marker trait

 

Implemented if:

  1. All fields implement the trait
  2. There is no negative implementation
unsafe auto trait Send {}

unsafe auto trait Sync {}
fn main() {
	
    let a: Rc<Book> = Rc::new(Book {
        name: "Dune".into(),
        isbn: 441172717,
    });
    
    let b = a.clone();

}

Rc and Arc

ref_count
inner name
isbn
ptr

Rc

a

ptr

Rc

b

fn main() {
    let my_rc = std::rc::Rc::new(0);
    
    std::thread::spawn(move || {
    	println!("{}", my_rc);
    });
}
error[E0277]: `Rc<i32>` cannot be sent between threads safely
   --> src/main.rs:4:5
    |
4   |       std::thread::spawn(move || {
    |  _____^^^^^^^^^^^^^^^^^^_-
    | |     |
    | |     `Rc<i32>` cannot be sent between threads safely
5   | |         println!("{}", my_rc);
6   | |     });
    | |_____- within this `[closure@src/main.rs:4:24: 6:6]`
    |
    = help: within `[closure@src/main.rs:4:24: 6:6]`, the 
            trait `Send` is not implemented for `Rc<i32>`
    = note: required because it appears within the type `
            [closure@src/main.rs:4:24: 6:6]`

cargo run

fn main() {
    let my_rc = std::sync::Arc::new(0);
    
    std::thread::spawn(move || {
    	println!("{}", my_rc);
    });
}

cargo run

0
fn main() {
    let my_rc = std::sync::Arc::new(0);
    
    std::thread::spawn(move || {        
        *my_rc += 1;
    });
}
error[E0594]: cannot assign to data in an `Arc`
 --> src/main.rs:5:9
  |
5 |         *my_rc += 1;
  |         ^^^^^^^^^^^ cannot assign
  |
  = help: trait `DerefMut` is required to modify 
          through a dereference, but it is not 
          implemented for `Arc<i32>`

cargo run

fn main() {
    let my_rc = std::sync::Arc::new(
        std::sync::Mutex::new(0));
    
    let my_rc_cloned = my_rc.clone();
    let handle = std::thread::spawn(move || {
        *my_rc_cloned.lock().unwrap() += 1;
    });
    
    handle.join().unwrap();
    println!("{}", my_rc.lock().unwrap());
}

cargo run

1
use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::sync_channel(2);
    
    for thread_num in 0..4 {
        let tx_instance = tx.clone();
        
        thread::spawn(move || {
            for n in 0..2 {
                tx_instance.send((thread_num, n)).unwrap();
            }
        });
    }
    
    for _ in 0..8 {
        let item = rx.recv().unwrap();
        println!("{:?}", item);
    }   
}

Channels

(0, 0)
(0, 1)
(2, 0)
(2, 1)
(3, 0)
(3, 1)
(1, 0)
(1, 1)

Async & Await

use std::path::Path;
use anyhow::Error;
use tokio::{fs::File, io::AsyncReadExt};

async fn read_file_to_string(
    path: &Path
) -> Result<String, Error> {

    let mut file = File::open("foo.txt").await?;

    let mut contents = String::new();
    file.read_to_string(&mut contents).await?;

	Ok(contents)    
}
use std::path::Path;
use anyhow::Error;
use tokio::{fs::File, io::AsyncReadExt};

async fn read_file_to_string(
    path: &Path
) -> Result<String, Error> {

    let mut file = File::open("foo.txt").await?;

    let mut contents = String::new();
    file.read_to_string(&mut contents).await?;

	Ok(contents)    
}

Function Call

Function Return

.await

point

state #0

state #1

state #2

state #3

state #4

enum FutureState {
    State0 {
        local1: i32,
        local2: String,
        [...]
    },
    State1 { [...] },
    State2 { [...] },
    State3 { [...] },
    State4 { [...] },
}

tokio

Crate:

Unsafe and FFI

let val: u8 = 8;

let my_ptr: *const u8 = &val;
  • Dereference a raw pointer
  • Call an unsafe function or method
  • Access or modify a mutable static variable
  • Implement an unsafe trait
  • Access fields of unions

Unsafe Code

unsafe fn my_unsafe_fun() {
    // unsafe code here
}

fn my_fun() {
    unsafe {
        // unsafe code here
    }
}
unsafe impl Sync for MyType {}
  • Unsafe code
  • Extern functions
    • C -> R
    • R -> C
#[no_mangle]
unsafe extern "C" fn sum_numbers(buf: *const u32, len: usize) -> u32 {
    let slice = std::slice::from_raw_parts(buf, len);
    
    slice
        .iter()
        .sum()
}
use std::os::raw::c_char;
use std::ffi::CString;

extern "C" {
    fn atof(string: *const c_char) -> f64;
}

fn main() {
    let c_string = CString::new("1.5").unwrap();
    
    let num = unsafe {
        atof(c_string.as_ptr())
    };
    
    println!("{}", num);
}

bindgen

Crate:

cbindgen

Crate:

cxx

Crate:

union

Common std types

std

struct Book {
    name: String,
    isbn: u64,
}

fn main() {
	
    let a: Box<Book> = Box::new(Book {
        name: "Dune".into(),
        isbn: 441172717,
    });

}

Box

name
isbn

Book

ptr

Box

a

Tests

#[test]
fn a_unit_test() {
    assert!(1 + 2 == 3)
}
#[test]
fn a_unit_test() {
    assert!(1 + 2 == 3)
}

cargo test

running 1 test
test a_unit_test ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

assert!(cond)

 

assert_eq!(lhs, rhs)

 

#[should_panic]

Allocators?

...

https://doc.rust-lang.org/std/string/struct.String.html

fn main() {
    let my_option = Some(5);
    
    match my_option {
        Some(num) => println!("Option contained {}", num),
        None => println!("Option was None"),
    }
}
enum Either<A, B> {
    Left(A),
    Right(B),
}

Rust for C++ Developers - Itema Day 2

By Hans Elias Bukholm Josephsen

Rust for C++ Developers - Itema Day 2

  • 181