2021-06-11
We will do this once or twice to test it out first before we commit to this.
Note: Most code snippets here are written as if they belong to a context or inside a scope / function.
This an exercise in learning Rust together on stream. We will do this for one hour on [date tbc], and announce this on the Discord server and in chat one hour in advance.
Goal: Multi threaded server / client chat
To have a basic understanding of Rust and to be able to
create something more than just hello world
.
Mutex
and Arc
* Goal 1: cover 1 - 9 A server that can accept connections
* Goal 2: cover 10 - 15 Sending / receiving messages
* Goal 3: cover 16 - 17 Error handling and proper termination of clients
* Goal 4: cover 18 - ...
cargo new helloworld
cargo run
cargo build
./target/debug/helloworld
cargo new --lib common
Talk about:
usize
/ isize
(why do we have it)let x = 1;
let mut y = 2;
Talk about:
&
&mut
*
will dereferencelet mut byte = 0u8;
change(&mut byte);
fn change(b: &mut u8) {
*b = 1; // deref `b` and give it a new value
}
Talk about:
if
loop
let cond = true;
if cond {
// body
}
loop {
}
while cond {
}
for i in 0..100 {
}
match
also works for loop
but not for for
and while
let value = 1u8;
match value {
0 => {}
1 => {}
5 => {}
_ => { /* Wildcard */ }
}
let value = true;
let data = match value {
true => 1,
false => 2,
}; // semicolon is important
A function
Talk about:
!
means we are calling macrofn do_thing() {
}
A function with one or more arguments
fn do_thing(arg1: u32, arg2: u32) {
}
A function with a return value
fn do_thing() -> u32 {
return 123;
}
fn do_thing_implicit_return() -> u32 {
123
}
A closure
let cl = |arg1: u32| {
println!("{}", arg1);
};
let string = "Hello closure".to_string();
cl(string);
Talk about:
Importing a single path
use std::net::TcpStream;
Importing multiple paths from the same module
use std::net::{TcpStream, TcpListener};
Importing a path under another name
use std::net::TcpStream as Stream;
Talk about:
Vec<T>
or [T]
(array):
Creating vectors
// Create a new vector
let mut a_vec: Vec<usize> = Vec::new();
// Create a new vector with a capacity of ten.
let mut a_vec: Vec<usize> = Vec::with_capacity(10);
// Create a new vector with a capacity of two and two elements
let mut a_vec: Vec<usize> = vec![1, 2];
// Create a new vector with a capacity of 200, and it's full of ones
let mut a_vec: Vec<usize> = vec![1; 200];
Creating an array
// An array with three bytes in it
let array_of_bytes = [0u8, 1, 2, 3];
// An array with 200 bytes in it, where every byte is zero
let array_of_bytes = [0u8; 200];
Creating a slice
let data = vec![1u8, 2, 3, 4];
// We specify the type here to prevent
let slice = &data[1..2]; // a slice containing 2, 3
Looping over all the values in a vector.
If we didn't use a reference (&
) we would consume the vector
let v = vec![1, 2];
for value in &v {
println!("{}", value);
}
Pushing and popping.
Popping returns an Option<T>
let mut v = vec![];
v.push(1); // v = vec![1]
v.push(2); // v = vec![1, 2]
v.pop(); // v = vec![1]
v.pop(); // v = vec![]
Talk about:
String
in Rust is always valid utf8len()
function of a string returns number of bytes (not number of chars)String
can deref to a &str
String
methods: trim
, split_whitespace
, push_str
str
methods: starts_with
, ends_with
, contains
String
and &str
let string = String::new();
let string_slice: &str = &string;
let string_slice = string.as_str();
let string_slice = &string[..];
A struct is a way to organise data
Talk about:
Note: Structs can't contain fields that are of the same type as the struct, as the compiler has to know the size of the struct at compile time. This would create an infinitely sized struct.
struct Thing {
field_1: u8,
field_2: u8,
}
We can add methods to structs
impl Thing {
fn do_thing(&self) {
let sum = self.field_1 + self.field_2;
println!("sum: {}", sum);
}
}
Tuple struct
struct Pos(u32, u32);
let pos = Pos(12, 33);
let (x, y) = (pos.0, pos.1);
println!("x: {}, y: {}", x, y);
New type pattern
struct UserId(u32);
let user_id = UserId(123);
let val: u32 = user_id.0;
Destructure a struct
struct Values {
a: u32,
b: u32,
c: u32,
d: u32,
}
let values = Values { a: 1, b: 2, c: 3, d: 4 };
let Values { a, c, .. } = values;
println!("a: {}, c: {}", a, c);
Talk about:
Result
and Option
map
and map_err
on Result
and Option
enum State {
Stopped,
Running(String),
Paused { duration: u32, count: u32 }
SubState(State)
}
Enums combined with match
makes for happy times
enum Food {
Hamburger,
AvocadoToast,
}
let food = Food::Hamburger;
let mut burger_counter = 0;
match food {
Food::Hamburger => {
printlin!("I love hamburgers");
burger_counter += 1;
},
Food::AvocadoToast => println!("Avocado toast sounds like fun"),
}
Talk about:
Read
and Write
for IO)&dyn Trait
or, if moved: Box<dyn Trait>
)impl
Trait in arg posimpl
Trait in return postrait Describe {
fn describe(&self) -> String;
}
Talk about:
use std::sync::mpsc;
use std::thread;
let (sender, receiver) = mpsc::channel();
thread::spawn(move || {
loop {
if let Ok(val) = receiver.recv() {
println!("Received: {}", val);
}
}
});
let mut counter = 0usize;
loop {
sender.send(counter);
counter += 1;
thread::sleep_ms(1000);
}
Talk about:
Send
and Sync
)Mutex
and Arc
combinationsArc
without Mutex
, just has no mutabilityCreating a thread
use std::thread;
thread::spawn(|| {
printline!("Hello from another thread");
});
Arc
and Mutex
combo: modify a string in two different threads.
use std::sync::{Mutex, Arc};
use std::thread;
let s = String::new();
let s = Arc::new(Mutex::new(s));
// Thread 1
let s_clone = Arc::clone(&s);
thread::spawn(move || s_clone.lock().map(|mut s| s.push_str("A")));
// Thread 2
let s_clone = Arc::clone(&s);
thread::spawn(move || s_clone.lock().map(|mut s| s.push_str("B")));
Talk about:
Reading bytes from a file. A similar approach can be used to read bytes from a network socket (where reading zero bytes most likely means connection closed)
use std::io::Read;
use std::fs::File;
let mut buf = [0u8; 1024];
let file = File::open("/tmp/somefile.txt").expect("Failed to open the file");
let bytes_read = file.read(&mut buf).unwrap();
let file_content: &[u8] = &buf[..bytes_read];
Read an entire file as a string
use std::fs::read_to_string;
let file_content = read_to_string("/tmp/somefile.txt").unwrap();
Talk about:
iter
, into_iter
, iter_mut
map
is lazy and for_each
can't return anything but a unitIterate and produce a new Vec<T>
that has all the values squared
let mut v = vec![1, 2, 3];
let new_vec = v
.iter()
.map(|val| val * val)
.collect::<Vec<usize>>();
Modify all elements in a Vec<T>
let mut v = vec![1, 2, 3];
v.iter_mut().for_each(|val| val *= val);
Combining adaptors
let v = vec![1, 2, 3, 4];
let new_values = v
.iter()
.enumerate()
.filter(|number| number > 2)
.map(|number| number += 10)
.collect::<Vec<_>>();
Flatten an iterator
let v = vec![
vec![1, 2],
vec![3, 4],
];
for val in v.into_iter().flatten() {
eprintln!("{:?}", val);
}
Talk about:
// main.rs
mod parent {
mod child {
}
}
parent::child
File system layout for a module named "submod", which has its own sub module named "misc"
project
\_ submod \
\_ mod.rs
\_ misc.rs
\_ main.rs
Talk about:
drop
our selvesstruct A;
impl Drop for A {
fn drop(&mut self) {
println!("A was dropped");
}
}
Talk about:
thiserr
and anyhow
Implementing your own error type that works with the ?
operator
use std::io::Error as IoErr;
type Result<T> = std::result::Result<T, OurErr>;
pub enum OurErr {
Io(IoErr),
Misc(String),
}
impl From<IoErr> for OurErr {
fn from(e: IoErr) -> Self {
Self::Io(e)
}
}
Talk about:
Open docs in default browser:
cargo doc --open
Local version of std lib api docs and "The Book":
Open docs
rustup doc
Go directly to std lib api
rustup doc --std
Talk about:
'static
: As a trait bound, it means the type does not contain any non-static
references.struct Application<'a> {
name: &'a str
}
struct Config<'a> {
name: &'a str,
}
let app_name = "Blorp".to_string();
let app = Application { name: &app_name };
let config = Config { name: &app_name };