Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The power of the where clause

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.

Florian Gilcher

March 30, 2019
Tweet

More Decks by Florian Gilcher

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  7. Background

    View Slide

  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

    View Slide

  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

    View Slide

  10. 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

    View Slide

  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::(&P)
    — This calls print::(&S)
    10

    View Slide

  12. 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

    View Slide

  13. Where is not only on functions
    struct Point where P: Debug {
    x: P,
    y: P
    }
    12

    View Slide

  14. 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

    View Slide

  15. 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

    View Slide

  16. 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

    View Slide

  17. Patterns

    View Slide

  18. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  23. 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

    View Slide

  24. 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

    View Slide

  25. 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

    View Slide

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

    View Slide

  27. 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

    View Slide

  28. 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

    View Slide

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

    View Slide

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

    View Slide

  31. Refactoring
    Where clauses are a refactoring
    targets!
    29

    View Slide

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

    View Slide

  33. Genericness
    Finding the right level of
    genericness is important!
    31

    View Slide

  34. 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

    View Slide

  35. Advanced Examples

    View Slide

  36. Relationships
    Traits and bounds can be used
    to express relationships
    33

    View Slide

  37. State Machines
    State machine by @happyautomata
    34

    View Slide

  38. State Machines
    trait State { }
    trait TerminalState { }
    35

    View Slide

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

    View Slide

  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

    View Slide

  41. 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

    View Slide

  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

    View Slide

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

    View Slide

  44. 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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  48. One last thing
    45

    View Slide

  49. Thank you!
    • https://twitter.com/argorak
    • https://github.com/skade
    • https://speakerdeck.com/skade
    • fl[email protected]
    • https://ferrous-systems.com
    • https://rust-experts.com
    46

    View Slide