Slide 1

Slide 1 text

The power of the where clause Florian Gilcher Rust LATAM 2019 CEO and Rust Trainer Ferrous Systems GmbH 1

Slide 2

Slide 2 text

#[implementation] fn main() { //no need to program anymore } 2

Slide 3

Slide 3 text

Whoami • Florian Gilcher • https://twitter.com/argorak • https://github.com/skade • CEO https://asquera.de, https://ferrous-systems.com • Rust Programmer and Trainer: https://rust-experts.com • Mozillian • Previously 10 years of Ruby community work 3

Slide 4

Slide 4 text

Whoami • Started learning Rust in 2013 • Mostly out of personal curiosity • Co-Founded the Berlin usergroup • Organized RustFest and OxidizeConf • Project member since 2015, mostly Community team 4

Slide 5

Slide 5 text

Whoami This is my first talk on a Rust conference! 5

Slide 6

Slide 6 text

I want to make you competent at reading and writing where clauses! 6

Slide 7

Slide 7 text

Background

Slide 8

Slide 8 text

A simple example #[derive(Debug)] – struct Point { x: i32, y: i32 } fn print_point(p: &Point ˜) { println!("{:?}" —, p) } – Point implements Debug — The println macro requires the printed value to implement Debug ˜ We pass Point as a concrete type 7

Slide 9

Slide 9 text

What about squares? #[derive(Debug)] struct Square { x: i32, y: i32, width: i32 } – fn print_square(s: &Square) { println!("{:?}", s) } – That’s very repetitive. Computers are good at repetitive! 8

Slide 10

Slide 10 text

Let’s be generic – — fn print(s: &S) where ˜ S: Debug { println!("{:?}", s) } – The function is generic over a type variable S — It takes a reference to a value of the type S ˜ S can be any type, as long as it implements Debug. This is called a trait bound. 9

Slide 11

Slide 11 text

Let’s be generic fn main() { let p = Point { x: 0, y: 0 }; let s = Square { x: 0, y: 0, width: 0 }; print(&p); – print(&s); — } – This calls print::(&P) — This calls print::(&S) 10

Slide 12

Slide 12 text

Let’s be generic fn main() { let p = Point { x: 0, y: 0 }; let s = Square { x: 0, y: 0, width: 0 }; print::(&p); – print::(&s); — } Both functions are actually different and are created on use. A function logically existing and actually being compiled is two things. 11

Slide 13

Slide 13 text

Where is not only on functions struct Point

where P: Debug { x: P, y: P } 12

Slide 14

Slide 14 text

Detailed analysis fn some_function – where T: Trait, T: OtherTrait, — E: Trait + OtherTrait { } – In the where clase, T is an actual type — Types can be constrained multiple times Read: ”for every type pair T and E, where T is Send + Sync and E is Send” 13

Slide 15

Slide 15 text

Detailed analysis Because the left hand is an actual type, this works: fn takes_into_string(t: T) where String: From { let string = String::from(t); println!("{}", string); } 14

Slide 16

Slide 16 text

Associated types The where clause can be used to constrain the associated type of a trait: fn debug_iter(iter: I) where I: Iterator, I::Item: Debug { for item in iter { println!("{:?}", iter); } } 15

Slide 17

Slide 17 text

Patterns

Slide 18

Slide 18 text

Multiple impl Blocks You can gradually unlock API based on bounds. enum Result { Ok(T), Err(E), } impl Result { fn is_ok(&self) -> bool { // ... } fn is_err(&self) -> bool { // ... } } 16

Slide 19

Slide 19 text

Multiple impl Blocks impl Result where E: Debug { fn unwrap(self) -> T { match self { Ok(t) -> t, Err(e) -> panic!("Error: {:?}", e), } } } 17

Slide 20

Slide 20 text

Multiple impl Blocks impl Result where T: Default { fn unwrap_or_default(self) -> T { //... } } 18

Slide 21

Slide 21 text

API design Gradually unlocking features based on bounds as a common API strategy in Rust. 19

Slide 22

Slide 22 text

The thread API fn main() { let vec = vec![1,2,3,4]; let handler = std::thread::spawn(move { vec.iter().count() }); let number = handler.join().unwrap(); println!("{}", number); } 20

Slide 23

Slide 23 text

The thread API pub fn spawn(f: F) -> JoinHandle where F: FnOnce() -> T, – { /*...*/ } — pub fn spawn(f: &'a i32) -> JoinHandle<&'a i32> pub fn spawn(f: std::slice::Iter<'a, T>) -> JoinHandle – This is the closure — I’m cheating a little, this is the closure capture type The compiler checks all potential situations! 21

Slide 24

Slide 24 text

Opting out of Borrowing pub fn spawn(f: F) -> JoinHandle where F: FnOnce() -> T, – F: Send + 'static, — T: Send + 'static { //... } – This is the closure — Send is one of Rust concurrency guards — ’static asks for the type to own all data This is not cheating! 22

Slide 25

Slide 25 text

Late bounds struct Wrapper where I: Debug { inner: I } fn take_inner(w: Wrapper) -> I where I: Debug – { w.inner } – This is necessary because the type mandates item, it isn’t useful to the function 23

Slide 26

Slide 26 text

Late bounds How do we make sure I keep the bound, but don’t have to repeat it all times? 24

Slide 27

Slide 27 text

Late bounds struct Wrapper { inner: I } impl Wrapper { pub fn new(d: I) -> Self where I: Debug { Wrapper { inner: d } } pub fn inspect(&self) where I: Debug { println!("{:?}", self.inner); } } fn take_inner(w: Wrapper) -> I { w.inner } 25

Slide 28

Slide 28 text

Late bounds impl Wrapper { pub fn new(d: I) -> Self – where I: Debug { Wrapper { inner: d } } pub fn inspect(&self) — where I: Debug { println!("{:?}", self.inner); } } – new ensures that Wrapper only ever can be constructed with Debug types 26

Slide 29

Slide 29 text

Late bounds impl Wrapper where Inner: Debug { pub fn new(inner: Inner) -> Self { Wrapper { inner: inner } } pub fn inspect(&self) { { println("{:?}", self.inner); } } 27

Slide 30

Slide 30 text

Late bounds fn take_inner(w: Wrapper) -> I { w.inner } 28

Slide 31

Slide 31 text

Refactoring Where clauses are a refactoring targets! 29

Slide 32

Slide 32 text

Refactoring Don’t start out very generic, refactor towards it! 30

Slide 33

Slide 33 text

Genericness Finding the right level of genericness is important! 31

Slide 34

Slide 34 text

Terrible clauses impl Execute for Find> where R: Relation, Repos: Repository, Repos::Gateway: ExecuteOne, Self: Query { type FutureType = <::Gateway as ExecuteOne< Self::Parameters>>::Future; fn execute(&self, repos: &Repos) -> Self::FutureType where Repos: Stores { ExecuteOne::execute_query(repos.gateway(), &self) } } These things don’t out of the blue and are frequently changed. 32

Slide 35

Slide 35 text

Advanced Examples

Slide 36

Slide 36 text

Relationships Traits and bounds can be used to express relationships 33

Slide 37

Slide 37 text

State Machines State machine by @happyautomata 34

Slide 38

Slide 38 text

State Machines trait State { } trait TerminalState { } 35

Slide 39

Slide 39 text

State Machines trait TransitionTo where S: State, Self: State { fn transition(self) -> S; } trait Terminate where Self: TerminalState { fn terminate(self); } 36

Slide 40

Slide 40 text

State Machines struct Start; impl State for Start {} struct Loop; impl State for Loop {} struct Stop; impl State for Stop {} impl TerminalState for Stop {} 37

Slide 41

Slide 41 text

State Machines impl TransitionTo for Start { fn transition(self) -> Loop { } impl TransitionTo for Loop { fn transition(self) -> Loop { } impl TransitionTo for End { fn transition(self) -> End { } impl Terminate for End { fn terminate(self) { } 38

Slide 42

Slide 42 text

State Machines fn main() { let initial = Start; let next: Loop = initial.transition(); let next: Loop = next.transition(); //next.terminate() //let next: Start = next.transition(); let next: End = next.transition(); next.terminate() } The setup is a little involved, but usage is straight-forward and safe! https://hoverbear.org/2016/10/12/rust-state-machine-pattern/ 39

Slide 43

Slide 43 text

Databases pub trait Storage { fn query(&self, id: i64) -> Model } 40

Slide 44

Slide 44 text

Databases pub trait Stores : Storage { } pub trait Storage { fn query(&self, id: i64) -> Model where Self: Stores – { //.... } } – Forcing the implementor to further constrait what the database stores 41

Slide 45

Slide 45 text

Databases struct UsersDatabase { /* */ } impl Storage for UsersDatabase { /* */ } struct User { id: i64, name: String } struct Avatar { /* */ } impl Stores for UsersDatabase {} impl Stores for UsersDatabase {} 42

Slide 46

Slide 46 text

Databases fn main() { let db = UserDatabase::connect(); db.query::(1); // db.query::(2); } 43

Slide 47

Slide 47 text

Conclusion • Getting comfortable with the power of the where clause is important • Exactly picking which constraints you need where is key • There’s creative patterns of interplay 44

Slide 48

Slide 48 text

One last thing 45

Slide 49

Slide 49 text

Thank you! • https://twitter.com/argorak • https://github.com/skade • https://speakerdeck.com/skade • florian.gilcher@ferrous-systems.com • https://ferrous-systems.com • https://rust-experts.com 46