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;
}
}
- Implement a trait from this crate for a struct from this crate
- Implement a trait from another crate for a struct from this crate
- Implement a trait from this crate for a struct from another crate
- 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:
- All fields implement the trait
- 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
- 238