Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

$ cat ~/.profile GIT_AUTHOR_NAME=Florian Gilcher GIT_AUTHOR_EMAIL=florian@rustfest.eu TWITTER_HANDLE=argorak GITHUB_HANDLE=skade BLOG=skade.me YAKS=yakshav.es

Slide 4

Slide 4 text

• Backend Developer • Ruby Programmer since 2003 • Rust Programmer since 2013 • CEO asquera GmbH

Slide 5

Slide 5 text

• Community person • Rust UG Berlin/Karlsruhe • Search UG Berlin • Ex-chairman of Ruby Berlin e.V.

Slide 6

Slide 6 text

• Part of the global Rust community team • Organiser eurucamp/jrubyconf.eu • Organiser RustFest

Slide 7

Slide 7 text

Useful Rust

Slide 8

Slide 8 text

Ownership struct Meal { name: String } fn eat(m: Meal) { println!("Ate: {}", m.name); // m will be destroyed here }

Slide 9

Slide 9 text

fn eat(m: Meal) { println!("Ate: {}", m.name); drop(m) }

Slide 10

Slide 10 text

fn eat(m: Meal) { let mut mutated = m; mutated.name = String::from("Godzilla & Chips"); println!("Ate: {}", mutated.name); drop(m) }

Slide 11

Slide 11 text

This is also called "consuming".

Slide 12

Slide 12 text

Traits trait Edible { fn eat(self); fn describe(&self); }

Slide 13

Slide 13 text

impl Edible for Meal { fn eat(self) { println!("Ate: {}", self.name); } fn describe(&self) { println!("This is: {}", self.name); } }

Slide 14

Slide 14 text

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.

Slide 41

Slide 41 text

Example: CouchDB replication

Slide 42

Slide 42 text

CouchDB Replication protocol https://is.gd/couchdbrep

Slide 43

Slide 43 text

Describes the process to replicate a source database to a target database.

Slide 44

Slide 44 text

Replicator pub struct Replicator { source: String, target: String }

Slide 45

Slide 45 text

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?

Slide 50

Slide 50 text

impl Replicator { pub fn new(source: S, target: T) -> Replicator where S: Into, T: Into { Replicator { source: source.into(), target: target.into() } } }

Slide 51

Slide 51 text

fn main() { let r = Replicator::new("source_name", "target_name"); }

Slide 52

Slide 52 text

The VerifyPeers steps Given a replicator between two databases: • Check source existence • Check target existence • Create target if absent • Done

Slide 53

Slide 53 text

Can we describe that in Rust?

Slide 54

Slide 54 text

Defining states pub struct Start; pub struct SourceExisting; pub struct TargetExisting; pub struct TargetAbsent; pub struct VerifiedPeers;

Slide 55

Slide 55 text

A stateful wrapper pub struct VerifyPeers { replicator: Replicator, state: T }

Slide 56

Slide 56 text

impl VerifyPeers { pub fn transition(self, new_state: X) -> VerifyPeers { VerifyPeers { replicator: self.replicator, state: new_state } } }

Slide 57

Slide 57 text

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!”

Slide 75

Slide 75 text

How do we implement work? type Error = String;

Slide 76

Slide 76 text

impl VerifyPeers { pub fn check_source_existence(self) -> Result, Error> { if try!(check_source_existence()) { Ok(self.transition(SourceExisting)) } else { Err("Source doesn’t exist!".into()) } } }

Slide 77

Slide 77 text

Horray, we also got the failure state!

Slide 78

Slide 78 text

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

Slide 88

Slide 88 text

Real-world: Laze RS Experimental CouchDB toolkit, see: http://laze.rs

Slide 89

Slide 89 text

Conclusion Rust is sufficiently useful for patterns beyond the main focus to emerge.

Slide 90

Slide 90 text

Rust is focused on usability • Usability is tough if you want fine- grained control for the user • In systems programming, usability is not champion

Slide 91

Slide 91 text

Interview If you have a Rust project, private or commercial, I’d like to interview you.

Slide 92

Slide 92 text

twitter.com/argorak

Slide 93

Slide 93 text

http://community.rs/ community-team@rust-lang.org rust-lang.org

Slide 94

Slide 94 text

Links • https://is.gd/statepatterns • https://is.gd/2017vision