Florian Gilcher
March 30, 2019
510

# The power of the where clause

A beginner-level description of how the where clause in Rust works and what it can be used for.

March 30, 2019

## Transcript

1. ### The power of the where clause Florian Gilcher Rust LATAM

2019 CEO and Rust Trainer Ferrous Systems GmbH 1

2
3. ### 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
4. ### 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

5
6. ### I want to make you competent at reading and writing

where clauses! 6

8. ### 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
9. ### 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
10. ### Let’s be generic   fn print<S>(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
11. ### 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::<Point>(&P)  This calls print::<Square>(&S) 10
12. ### Let’s be generic fn main() { let p = Point

{ x: 0, y: 0 }; let s = Square { x: 0, y: 0, width: 0 }; print::<Point>(&p);  print::<Square>(&s);  } Both functions are actually different and are created on use. A function logically existing and actually being compiled is two things. 11
13. ### Where is not only on functions struct Point<P> where P:

Debug { x: P, y: P } 12
14. ### Detailed analysis fn some_function<T,E>  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
15. ### Detailed analysis Because the left hand is an actual type,

this works: fn takes_into_string<T>(t: T) where String: From<T> { let string = String::from(t); println!("{}", string); } 14
16. ### Associated types The where clause can be used to constrain

the associated type of a trait: fn debug_iter<I>(iter: I) where I: Iterator, I::Item: Debug { for item in iter { println!("{:?}", iter); } } 15

18. ### Multiple impl Blocks You can gradually unlock API based on

bounds. enum Result<T, E> { Ok(T), Err(E), } impl<T, E> Result<T, E> { fn is_ok(&self) -> bool { // ... } fn is_err(&self) -> bool { // ... } } 16
19. ### Multiple impl Blocks impl<T, E> Result<T, E> where E: Debug

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

{ fn unwrap_or_default(self) -> T { //... } } 18
21. ### API design Gradually unlocking features based on bounds as a

common API strategy in Rust. 19
22. ### 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
23. ### The thread API pub fn spawn<F, T>(f: F) -> JoinHandle<T>

where F: FnOnce() -> T,  { /*...*/ }  pub fn spawn(f: &'a i32) -> JoinHandle<&'a i32> pub fn spawn(f: std::slice::Iter<'a, T>) -> JoinHandle<usize>  This is the closure  I’m cheating a little, this is the closure capture type The compiler checks all potential situations! 21
24. ### Opting out of Borrowing pub fn spawn<F, T>(f: F) ->

JoinHandle<T> 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
25. ### Late bounds struct Wrapper<I> where I: Debug { inner: I

} fn take_inner<I>(w: Wrapper<I>) -> I where I: Debug  { w.inner }  This is necessary because the type mandates item, it isn’t useful to the function 23
26. ### Late bounds How do we make sure I keep the

bound, but don’t have to repeat it all times? 24
27. ### Late bounds struct Wrapper<I> { inner: I } impl<I> Wrapper<I>

{ 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<I>(w: Wrapper<I>) -> I { w.inner } 25
28. ### Late bounds impl<I> Wrapper<I> { 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
29. ### Late bounds impl<Inner> Wrapper<Inner> where Inner: Debug { pub fn

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

28

34. ### Terrible clauses impl<Repos, R, ReturnType, PK> Execute<Repos, R, ReturnType> for

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

33

35
39. ### State Machines trait TransitionTo<S> where S: State, Self: State {

fn transition(self) -> S; } trait Terminate where Self: TerminalState { fn terminate(self); } 36
40. ### 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
41. ### State Machines impl TransitionTo<Loop> for Start { fn transition(self) ->

Loop { } impl TransitionTo<Loop> for Loop { fn transition(self) -> Loop { } impl TransitionTo<End> for End { fn transition(self) -> End { } impl Terminate for End { fn terminate(self) { } 38
42. ### 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

Model } 40
44. ### Databases pub trait Stores<Model> : Storage { } pub trait

Storage { fn query<Model>(&self, id: i64) -> Model where Self: Stores<Model>  { //.... } }  Forcing the implementor to further constrait what the database stores 41
45. ### Databases struct UsersDatabase { /* */ } impl Storage for

UsersDatabase { /* */ } struct User { id: i64, name: String } struct Avatar { /* */ } impl Stores<User> for UsersDatabase {} impl Stores<Avatar> for UsersDatabase {} 42
46. ### Databases fn main() { let db = UserDatabase::connect(); db.query::<User>(1); //

db.query::<String>(2); } 43
47. ### 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

49. ### Thank you! • https://twitter.com/argorak • https://github.com/skade • https://speakerdeck.com/skade • ﬂ[email protected]

• https://ferrous-systems.com • https://rust-experts.com 46