fn main() {
let meal = Meal {
name: String::from("Fish & Chips")
};
meal.describe();
meal.eat();
meal.eat();
//~^ ERROR use of moved value: `meal`
}
Slide 15
Slide 15 text
Generics
fn eat_anything(f: F) {
f.eat();
}
Slide 16
Slide 16 text
fn eat_anything(f: F)
where F: Edible {
f.eat();
}
Slide 17
Slide 17 text
Generic calls are monomorphised
and have no runtime overhead.
Slide 18
Slide 18 text
struct Course {
items: Vec
}
Slide 19
Slide 19 text
Items must be homogenous.
Slide 20
Slide 20 text
Errors and Results
Slide 21
Slide 21 text
We call algebraic datatypes "enums".
Slide 22
Slide 22 text
enum Option {
Some(T),
None
}
Slide 23
Slide 23 text
enum Result {
Ok(T),
Err(E)
}
Slide 24
Slide 24 text
Enough theory
Slide 25
Slide 25 text
Example: JSON
Slide 26
Slide 26 text
decode json_string :: Maybe MyData
Slide 27
Slide 27 text
“ Hey, describe me the data in
the way we both know best,
and I will derive the rest.”
Slide 28
Slide 28 text
extern crate serde_json;
// define MyApiMessage
fn main() {
use serde_json::from_str;
let res = from_str::("...");
}
Slide 29
Slide 29 text
• Uses internal type information
• Fails early if the received message
is unknown
• Does not raise exceptions
Slide 30
Slide 30 text
Who likes
checked
exceptions?
Slide 31
Slide 31 text
I do
Slide 32
Slide 32 text
fn main() {
use serde_json::from_str;
let res = from_str::("...");
match res {
Ok(message) => ...,
Err(e) => println!("Error: {}", e)
}
}
Slide 33
Slide 33 text
Rust ships with a lot of machinery
around converting errors, though...
Slide 34
Slide 34 text
pub trait From: Sized {
fn from(T) -> Self;
}
Slide 35
Slide 35 text
impl From for MyDomainModel {
fn from(T) -> Self {
// here be alpaca
}
}
Slide 36
Slide 36 text
fn main() {
use serde_json::from_str;
let res = from_str::("...")
.and_then(|message|{
MyDomainModel::from(message)
})
//~^ WARNING: unused result which must be
used
}
Slide 37
Slide 37 text
Checking the compiler:
compile-test
https://is.gd/compiletest
Slide 38
Slide 38 text
fn main() {
use serde_json::from_str;
let res = from_str::("...")
.and_then(|message|{
MyDomainModel::from(message)
})
//~^ WARNING: unused result which must be
used
}
Slide 39
Slide 39 text
It’s not unusual for libraries to
have test suites for the cases that
should error/warn at complain time.
Slide 40
Slide 40 text
It’s not unusual for libraries to
have test suites for the cases that
should error/warn at compile time.
fn main() {
let r = Replicator {
source: "source_name".into(),
target: "target_name".into()
}
}
Slide 46
Slide 46 text
Inconvenient
• Initialisation depending on exact
structure
• Struct fields are by default private,
this is not going to work outside of
the defining module.
Slide 47
Slide 47 text
Seperation of
functions and state
impl Replicator {
pub fn new(s: String, t: String)
-> Replicator {
Replicator { source: s, target: t }
}
}
Slide 48
Slide 48 text
fn main() {
let r = Replicator::new(
"source_name".into(),
"target_name".into()
);
}
Slide 49
Slide 49 text
This is still a bit
inconvenient
Can we get rid of the into() calls?
let replicator = Replicator::new("source_name",
"target_name");
let verification = VerifyPeers::new(replicator);
let end = verification
.transition(SourceExisting)
.transition(TargetExisting)
.transition(VerifiedPeers);
Slide 58
Slide 58 text
Safety gains
Slide 59
Slide 59 text
let replicator = Replicator::new("source_name",
"target_name");
let verification = VerifyPeers::new(replicator);
let end = verification
.transition(SourceExisting)
.transition(TargetExisting)
.transition(VerifiedPeers);
verification.transition(SourceExisting);
//~^ ERROR use of moved value: `verification`
Slide 60
Slide 60 text
Obvious flaws
Slide 61
Slide 61 text
Nonsensical transitions
struct Counter {
count: i32
}
fn main() {
let r = Replicator::new("source",
"target");
let v = VerifyPeers::new(r);
let counter = Counter { count: 0 };
let end = v
.transition(counter);
}
Slide 62
Slide 62 text
Illegal transitions
let end = verification
.transition(VerifiedPeers)
.transition(SourceExisting);
Slide 63
Slide 63 text
Marking states
pub trait State {}
impl State for Start {}
impl State for SourceExisting {}
impl State for TargetExisting {}
impl State for TargetAbsent {}
impl State for VerifiedPeers {}
Slide 64
Slide 64 text
Constraining to States
impl VerifyPeers {
pub fn transition(self, new_state: X)
-> VerifyPeers
where X: State {
VerifyPeers {
replicator: self.replicator,
state: new_state
}
}
}
Slide 65
Slide 65 text
Now failing
struct Counter {
count: i32
}
fn main() {
let r = Replicator::new("source",
"target");
let v = VerifyPeers::new(r);
let counter = Counter { count: 0 };
let end = v
.transition(counter);
}
Slide 66
Slide 66 text
“ the trait bound Counter:
statemachines::State is not satisfied”
Slide 67
Slide 67 text
Constraining
Transitions
Rust conversion traits:
• From, TryFrom
• Into, TryInto
Use is very common!
Slide 68
Slide 68 text
We can use them to
model state transitions.
Slide 69
Slide 69 text
impl From for SourceExisting {
fn from(_: Start) -> SourceExisting {
SourceExisting
}
}
Slide 70
Slide 70 text
We implement in the same fashion:
• SourceExisting -> TargetAbsent
• SourceExisting -> TargetExisting
• TargetAbsent -> TargetExisting
• TargetExisting -> VerifiedPeers
Slide 71
Slide 71 text
impl VerifyPeers {
pub fn transition(self, new_state: X)
-> VerifyPeers
where X: State + From {
VerifyPeers {
replicator: self.replicator,
state: new_state
}
}
}
Slide 72
Slide 72 text
Now failing
fn main() {
let r = Replicator::new("source",
"target");
let v = VerifyPeers::new(r);
let end = v
.transition(VerifiedPeers);
}
Slide 73
Slide 73 text
“ the trait bound
statemachines::VerifiedPeers: std::con-
vert::From
is not satisfied”
Slide 74
Slide 74 text
“ I was promised Useful Rust and all I
got was a lousy state machine!”
How do we
implement a branch?
pub enum TargetChecked {
TargetExists(VerifyPeers),
TargetAbsent(VerifyPeers)
}
Slide 79
Slide 79 text
impl VerifyPeers {
pub fn check_target_existence(self) ->
Result {
if try!(target_exists()) {
Ok(self.transition(TargetExisting))
} else {
Ok(self.transition(TargetAbsent))
}
}
}
Slide 80
Slide 80 text
extern crate statemachines;
use statemachines::*;
fn main() {
let r = Replicator::new("source",
"target");
let v = VerifyPeers::new(r);
let end = v
.check_source_existence()
.and_then(|state| {
state.check_target_existence()
}).and_then(|state| {
//...
}
Slide 81
Slide 81 text
.and_then(|state| {
match state {
TargetChecked::TargetExists(s) => {
s.finish()
}
TargetChecked::TargetAbsent(s) => {
try!(s.create_target()).finish()
}
}
});
Slide 82
Slide 82 text
Free
This abstraction is free!
Slide 83
Slide 83 text
• Fully at compile-time
• Vanishes at runtime
Slide 84
Slide 84 text
A bit of low-level
#[test]
fn verify_peers_not_larger_then_replicator()
{
use std::mem::size_of;
assert_eq!(size_of::>(),
size_of::())
}
Slide 85
Slide 85 text
Final comments
Slide 86
Slide 86 text
• We’re exclusively using static dis-
patch
• The whole state machine is on the
stack
Slide 87
Slide 87 text
• This pattern extends well and also
works with, e.g. Futures instead of
Results
• Other implementations are possible