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

Ownership: the code principle for api design

Ownership: the code principle for api design

Rust's most distinguishing feature is the concept of ownership. It is a novel feature that allows Rust to achieve the trifecta of speed, safety and concurrency. In this talk, I trace the motivation for ownership, what it is and why it should be one of the core principals when you are designing your API.

Apoorv Kothari

September 13, 2017
Tweet

More Decks by Apoorv Kothari

Other Decks in Technology

Transcript

  1. Ownership in Rust:
    the core principle for API design
    Apoorv Kothari

    View full-size slide

  2. content
    • what is ownership?
    • motivation for ownership
    • relevant ownership rules
    • designing an API

    View full-size slide

  3. what is ownership?

    View full-size slide

  4. what is ownership?

    View full-size slide

  5. what is ownership?

    View full-size slide

  6. • `value` is a property which says that foo is equal to 5
    • `ownership` is a property which says foo owns 5
    • purely a compile-time property (no runtime cost)
    let foo = 5;
    what is ownership?

    View full-size slide

  7. let foo = String::from("data");
    what is ownership?

    View full-size slide

  8. let foo = String::from("data");
    let bar = foo; // move ownership
    what is ownership?

    View full-size slide

  9. let foo = String::from("data");
    let bar = foo; // move ownership
    println!(“{}”, foo); // try to use moved value
    what is ownership?

    View full-size slide

  10. let foo = String::from("data");
    let bar = foo; // move ownership
    println!(“{}”, foo); // try to use moved value
    9 | let bar = foo;
    | --- value moved here
    10 | // invalid because foo no longer owns “data”
    11 | println!("{}", foo);
    | ^^^ value used here after move
    what is ownership?

    View full-size slide

  11. let foo = String::from("data");
    let bar = foo; // move ownership
    println!(“{}”, foo); // try to use moved value
    9 | let bar = foo;
    | --- value moved here
    10 | // invalid because foo no longer owns “data”
    11 | println!("{}", foo);
    | ^^^ value used here after move
    what is ownership?

    View full-size slide

  12. let foo = String::from("data");
    let bar = foo; // move ownership
    println!(“{}”, foo); // try to use moved value
    9 | let bar = foo;
    | --- value moved here
    10 | // invalid because foo no longer owns “data”
    11 | println!("{}", foo);
    | ^^^ value used here after move
    what is ownership?

    View full-size slide

  13. well actually… ownership/borrow/lifetimes
    • these three make up the “ownership system” in Rust
    • we will only cover ownership and borrow
    • lifetimes:
    • is a concept used by the compiler
    • to measure when borrowed data goes of scope and
    • recover memory once data is finished being borrowed

    View full-size slide

  14. motivation for ownership

    View full-size slide

  15. • stack
    • u32, i32, bool, char, &, etc.
    • fast
    • limited space
    stack vs heap
    Stack Heap
    let num = 5;
    5
    'c'
    7

    View full-size slide

  16. stack vs heap
    • stack
    • u32, i32, bool, char, &, etc.
    • fast
    • limited space
    • heap
    • String, Vector
    • slow
    • *unlimited size
    Stack Heap
    let v = vec!(1,2,3);
    let hi =
    String::from("Hi");
    5
    &v
    'c'
    &hi
    7
    vec!(1,2,3)
    "Hi"

    View full-size slide

  17. byte vs deep copy
    • byte copy is the default behavior
    • copy the bytes representing 5
    Stack Heap
    let num1 = 5;
    let num2 = num1;
    5
    byte copy
    5
    'c'

    View full-size slide

  18. Stack Heap
    let v1 = vec!(1,2,3);
    vec!(1,2,3)
    5
    let v2 = v1.clone();
    &v2
    deep copy
    vec!(1,2,3)
    5
    &v1
    'c'
    &v2
    byte vs deep copy
    • byte copy is the default behavior
    • copy the bytes representing 5
    • deep copy is necessary for data
    living on the heap
    • follow all the pointers and copy
    data they point to

    View full-size slide

  19. implication of byte copy
    Stack Heap
    let v1 = vec!(1,2,3);
    5
    &v1
    c
    vec!(1,2,3)
    7
    let v2 = v1;
    &v2
    byte copy

    View full-size slide

  20. Stack Heap
    let v1 = vec!(1,2,3);
    5
    &v1
    c
    vec!(1,2,3)
    7
    let v2 = v1;
    &v2
    byte copy
    implication of byte copy
    • overwriting data (multi-threaded access)
    • undefined behavior (if vector length is
    changed)
    • access neighbors data (compromised
    security)
    • corrupting of data

    View full-size slide

  21. solutions to the ‘copy’ problem
    manage memory and use a GC process (Java) no yes
    Copy Options Speed Safety
    do a byte copy let the developer handle it (C) yes no
    always do a deep copy no yes
    invalidate previous variable by moving ownership (Rust) yes yes

    View full-size slide

  22. relevant ownership rules

    View full-size slide

  23. ways to share data in Rust
    let foo = String::from("data");
    let bar = foo;
    // foo is invalidated
    move 

    (ownership semantic)

    View full-size slide

  24. ways to share data in Rust
    let foo = String::from("data");
    let bar = foo;
    // foo is invalidated
    let foo = String::from("data");
    let bar = &foo;
    // foo is valid
    move 

    (ownership semantic)
    reference (&T) 

    (borrow semantic)

    View full-size slide

  25. move 

    (ownership semantic)
    let mut foo = String::from("data");
    let bar = &mut foo;
    // foo is valid, but only 1 ref allowed
    mutable ref (&mut T) 

    (borrow semantic)
    ways to share data in Rust
    let foo = String::from("data");
    let bar = foo;
    // foo is invalidated
    let foo = String::from("data");
    let bar = &foo;
    // foo is valid
    reference (&T) 

    (borrow semantic)

    View full-size slide

  26. move (ownership)
    reference (&T) (borrow)
    mutable ref (&mut T) (borrow)
    ways to share data in Rust
    let foo = String::from("data");
    let bar = foo;
    let foo = String::from("data");
    let bar = &foo;
    let mut foo = String::from("data");
    let bar = &mut foo;
    NO RUNTIME COSTS

    View full-size slide

  27. relevant ownership rules
    • each value has a variable which is its owner / 

    there can only be one owner at a time (ownership)
    • you can have as many &T as you want (borrow)
    • you can have only one &mut T in scope (borrow)

    View full-size slide

  28. each value has a variable which is its owner /
    there can only be one owner at a time
    relevant ownership rules
    let foo: String = String::from("data");
    let bar: String = foo; //ownership moved
    println!("{}", foo); //moved after use
    9 | let bar = foo;
    | --- value moved here
    10 | // invalid because foo no longer owns “data”
    11 | println!("{}", foo);
    | ^^^ value used here after move

    View full-size slide

  29. you can have as many &T as you want
    let foo = String::from("data");
    let bar = &foo; //create a reference
    println!("{}", foo); //foo still owns its data
    println!("{}", foo); //multiple reference allowed
    relevant ownership rules

    View full-size slide

  30. you can have only one &mut T in scope
    let mut foo = String::from("data");
    let bar = &mut foo; //create a mut reference
    println!("{}", &foo); //only 1 mut reference allowed
    relevant ownership rules
    37 | let bar = &mut foo; //create a mut reference
    | --- mutable borrow occurs here
    38 | println!("{}", foo); //only 1 mut reference allowed
    | ^^^ immutable borrow occurs here
    40 | }
    | - mutable borrow ends here

    View full-size slide

  31. designing an API

    View full-size slide

  32. guarantees of the ownership system
    • move: owner controls how long the data is valid for

    View full-size slide

  33. guarantees of the ownership system
    • move: owner controls how long the data is valid for
    • &T: multiple immutable reads are possible

    View full-size slide

  34. guarantees of the ownership system
    • move: owner controls how long the data is valid for
    • &T: multiple immutable reads are possible
    • &mut T: mutation is possible and there is only one &mut in scope!

    View full-size slide

  35. bank API
    Account
    Balance
    value
    Transactions
    amount
    to_acc from_acc
    transfer()
    check_accounts()
    user_name
    execute()

    View full-size slide

  36. struct Account {
    user_name: String,
    balance: Balance,
    }
    struct Balance {
    value: u64,
    }
    bank API

    View full-size slide

  37. struct Account {
    user_name: String,
    balance: Balance,
    }
    struct Balance {
    value: u64,
    }
    bank API

    View full-size slide

  38. struct Balance {
    value: u64,
    }
    struct Account {
    user_name: String,
    balance: Balance,
    }
    bank API

    View full-size slide

  39. impl<'a> Transaction<'a> {
    fn execute(&mut self) {
    self.from.balance.value -= self.amount;
    self.to.balance.value += self.amount;
    }
    }
    struct Transaction<'a> {
    from: &'a mut Account,
    to: &'a mut Account,
    amount: u64,
    }
    bank API

    View full-size slide

  40. struct Transaction<'a> {
    from: &'a mut Account,
    to: &'a mut Account,
    amount: u64,
    }
    impl<'a> Transaction<'a> {
    fn execute(&mut self) {
    self.from.balance.value -= self.amount;
    self.to.balance.value += self.amount;
    }
    }
    bank API

    View full-size slide

  41. impl<'a> Transaction<'a> {
    fn execute(&mut self) {
    self.from.balance.value -= self.amount;
    self.to.balance.value += self.amount;
    }
    }
    struct Transaction<'a> {
    from: &'a mut Account,
    to: &'a mut Account,
    amount: u64,
    }
    bank API

    View full-size slide

  42. impl<'a> Transaction<'a> {
    fn execute(&mut self) {
    self.from.balance.value -= self.amount;
    self.to.balance.value += self.amount;
    }
    }
    struct Transaction<'a> {
    from: &'a mut Account,
    to: &'a mut Account,
    amount: u64,
    }
    bank API

    View full-size slide

  43. fn check_accounts(acc: Vec<&Account>) {
    for i in acc {
    println!("{:?}", i);
    }
    }
    fn transfer(
    mut from: &mut Account,
    mut to: &mut Account,
    amount: u64) {
    Transaction { from, to, amount }.execute();
    }
    bank API

    View full-size slide

  44. fn check_accounts(acc: Vec<&Account>) {
    for i in acc {
    println!("{:?}", i);
    }
    }
    fn transfer(
    mut from: &mut Account,
    mut to: &mut Account,
    amount: u64) {
    Transaction { from, to, amount }.execute();
    }
    bank API

    View full-size slide

  45. fn check_accounts(acc: Vec<&Account>) {
    for i in acc {
    println!("{:?}", i);
    }
    }
    fn transfer(
    mut from: &mut Account,
    mut to: &mut Account,
    amount: u64) {
    Transaction { from, to, amount }.execute();
    }
    bank API

    View full-size slide

  46. bank API
    fn main() {
    let alice_balance = Balance { value: 100 };
    let mut alice_acc = Account { user_name: String::from("alice"), balance: alice_balance };
    let bob_balance = Balance { value: 100 };
    let mut bob_acc = Account { user_name: String::from(“bob"), balance: bob_balance };
    check_accounts(vec![&alice_acc, &bob_acc]);
    { /* TRANSACTION 1: Restrict the scope to the block. */
    let mut transfer = Transaction { from: &mut alice_acc, to: &mut bob_acc, amount: 10 };
    transfer.execute();
    }
    check_accounts(vec![&alice_acc, &bob_acc]);
    /* TRANSACTION 2: Restricted scope because Transaction is not assigned. */
    transfer(&mut alice_acc, &mut bob_acc, 10);
    check_accounts(vec![&alice_acc, &bob_acc]);
    }

    View full-size slide

  47. bank API
    fn main() {
    let alice_balance = Balance { value: 100 };
    let mut alice_acc = Account { user_name: String::from("alice"), balance: alice_balance };
    let bob_balance = Balance { value: 100 };
    let mut bob_acc = Account { user_name: String::from(“bob"), balance: bob_balance };
    check_accounts(vec![&alice_acc, &bob_acc]);
    ...
    }

    View full-size slide

  48. bank API
    fn main() {
    let alice_balance = Balance { value: 100 };
    let mut alice_acc = Account { user_name: String::from("alice"), balance: alice_balance };
    let bob_balance = Balance { value: 100 };
    let mut bob_acc = Account { user_name: String::from(“bob"), balance: bob_balance };
    check_accounts(vec![&alice_acc, &bob_acc]);
    ...
    }

    View full-size slide

  49. bank API
    fn main() {
    let alice_balance = Balance { value: 100 };
    let mut alice_acc = Account { user_name: String::from("alice"), balance: alice_balance };
    let bob_balance = Balance { value: 100 };
    let mut bob_acc = Account { user_name: String::from(“bob"), balance: bob_balance };
    check_accounts(vec![&alice_acc, &bob_acc]);
    ...
    }

    View full-size slide

  50. bank API
    fn main() {
    let alice_balance = Balance { value: 100 };
    let mut alice_acc = Account { user_name: String::from("alice"), balance: alice_balance };
    println!(“{:?}", alice_balance);
    let bob_balance = Balance { value: 100 };
    let mut bob_acc = Account { user_name: String::from(“bob"), balance: bob_balance };
    check_accounts(vec![&alice_acc, &bob_acc]);
    ...
    }

    View full-size slide

  51. bank API
    fn main() {
    let alice_balance = Balance { value: 100 };
    let mut alice_acc = Account { user_name: String::from("alice"), balance: alice_balance };
    println!(“{:?}", alice_balance);
    let bob_balance = Balance { value: 100 };
    let mut bob_acc = Account { user_name: String::from(“bob"), balance: bob_balance };
    check_accounts(vec![&alice_acc, &bob_acc]);
    ...
    }

    View full-size slide

  52. bank API
    fn main() {
    let alice_balance = Balance { value: 100 };
    let mut alice_acc = Account { user_name: String::from("alice"), balance: alice_balance };
    let bob_balance = Balance { value: 100 };
    let mut bob_acc = Account { user_name: String::from(“bob"), balance: bob_balance };
    check_accounts(vec![&alice_acc, &bob_acc]);
    ...
    }

    View full-size slide

  53. bank API
    fn main() {
    let alice_balance = Balance { value: 100 };
    let mut alice_acc = Account { user_name: String::from("alice"), balance: alice_balance };
    let bob_balance = Balance { value: 100 };
    let mut bob_acc = Account { user_name: String::from(“bob"), balance: bob_balance };
    check_accounts(vec![&alice_acc, &bob_acc]);
    ...
    }

    View full-size slide

  54. bank API
    fn main() {
    let mut alice_acc = Account { user_name: String::from("alice"), balance: alice_balance };
    let mut bob_acc = Account { user_name: String::from(“bob"), balance: bob_balance };
    ...
    { /* TRANSACTION 1: Restrict the scope to the block. */
    let mut transfer = Transaction { from: &mut alice_acc, to: &mut bob_acc, amount: 10 };
    transfer.execute();
    }
    check_accounts(vec![&alice_acc, &bob_acc]);
    /* TRANSACTION 2: Restricted scope because Transaction is not assigned. */
    transfer(&mut alice_acc, &mut bob_acc, 10);
    check_accounts(vec![&alice_acc, &bob_acc]);
    }

    View full-size slide

  55. bank API
    fn main() {
    let mut alice_acc = Account { user_name: String::from("alice"), balance: alice_balance };
    let mut bob_acc = Account { user_name: String::from(“bob"), balance: bob_balance };
    ...
    { /* TRANSACTION 1: Restrict the scope to the block. */
    let mut transfer = Transaction { from: &mut alice_acc, to: &mut bob_acc, amount: 10 };
    transfer.execute();
    }
    check_accounts(vec![&alice_acc, &bob_acc]);
    /* TRANSACTION 2: Restricted scope because Transaction is not assigned. */
    transfer(&mut alice_acc, &mut bob_acc, 10);
    check_accounts(vec![&alice_acc, &bob_acc]);
    }

    View full-size slide

  56. bank API
    fn main() {
    let mut alice_acc = Account { user_name: String::from("alice"), balance: alice_balance };
    let mut bob_acc = Account { user_name: String::from(“bob"), balance: bob_balance };
    ...
    { /* TRANSACTION 1: Restrict the scope to the block. */
    let mut transfer = Transaction { from: &mut alice_acc, to: &mut bob_acc, amount: 10 };
    transfer.execute();
    }
    check_accounts(vec![&alice_acc, &bob_acc]);
    /* TRANSACTION 2: Restricted scope because Transaction is not assigned. */
    transfer(&mut alice_acc, &mut bob_acc, 10);
    check_accounts(vec![&alice_acc, &bob_acc]);
    }

    View full-size slide

  57. bank API
    fn main() {
    let mut alice_acc = Account { user_name: String::from("alice"), balance: alice_balance };
    let mut bob_acc = Account { user_name: String::from(“bob"), balance: bob_balance };
    ...
    { /* TRANSACTION 1: Restrict the scope to the block. */
    let mut transfer = Transaction { from: &mut alice_acc, to: &mut bob_acc, amount: 10 };
    transfer.execute();
    } //`transfer` is dropped here
    check_accounts(vec![&alice_acc, &bob_acc]);
    /* TRANSACTION 2: Restricted scope because Transaction is not assigned. */
    transfer(&mut alice_acc, &mut bob_acc, 10);
    check_accounts(vec![&alice_acc, &bob_acc]);
    }

    View full-size slide

  58. bank API
    fn main() {
    let mut alice_acc = Account { user_name: String::from("alice"), balance: alice_balance };
    let mut bob_acc = Account { user_name: String::from(“bob"), balance: bob_balance };
    ...
    { /* TRANSACTION 1: Restrict the scope to the block. */
    let mut transfer = Transaction { from: &mut alice_acc, to: &mut bob_acc, amount: 10 };
    transfer.execute();
    }
    check_accounts(vec![&alice_acc, &bob_acc]);
    /* TRANSACTION 2: Restricted scope because Transaction is not assigned. */
    transfer(&mut alice_acc, &mut bob_acc, 10);
    check_accounts(vec![&alice_acc, &bob_acc]);
    }

    View full-size slide

  59. bank API
    fn main() {
    let mut alice_acc = Account { user_name: String::from("alice"), balance: alice_balance };
    let mut bob_acc = Account { user_name: String::from(“bob"), balance: bob_balance };
    ...
    { /* TRANSACTION 1: Restrict the scope to the block. */
    let mut transfer = Transaction { from: &mut alice_acc, to: &mut bob_acc, amount: 10 };
    transfer.execute();
    check_accounts(vec![&alice_acc, &bob_acc]);
    }
    /* TRANSACTION 2: Restricted scope because Transaction is not assigned. */
    transfer(&mut alice_acc, &mut bob_acc, 10);
    check_accounts(vec![&alice_acc, &bob_acc]);
    }

    View full-size slide

  60. bank API
    fn main() {
    let mut alice_acc = Account { user_name: String::from("alice"), balance: alice_balance };
    let mut bob_acc = Account { user_name: String::from(“bob"), balance: bob_balance };
    ...
    { /* TRANSACTION 1: Restrict the scope to the block. */
    let mut transfer = Transaction { from: &mut alice_acc, to: &mut bob_acc, amount: 10 };
    transfer.execute();
    check_accounts(vec![&alice_acc, &bob_acc]);
    }
    /* TRANSACTION 2: Restricted scope because Transaction is not assigned. */
    transfer(&mut alice_acc, &mut bob_acc, 10);
    check_accounts(vec![&alice_acc, &bob_acc]);
    }

    View full-size slide

  61. bank API
    fn main() {
    let mut alice_acc = Account { user_name: String::from("alice"), balance: alice_balance };
    let mut bob_acc = Account { user_name: String::from(“bob"), balance: bob_balance };
    ...
    { /* TRANSACTION 1: Restrict the scope to the block. */
    let mut transfer = Transaction { from: &mut alice_acc, to: &mut bob_acc, amount: 10 };
    transfer.execute();
    check_accounts(vec![&alice_acc, &bob_acc]);
    }
    /* TRANSACTION 2: Restricted scope because Transaction is not assigned. */
    transfer(&mut alice_acc, &mut bob_acc, 10);
    check_accounts(vec![&alice_acc, &bob_acc]);
    }

    View full-size slide

  62. bank API
    fn main() {
    let mut alice_acc = Account { user_name: String::from("alice"), balance: alice_balance };
    let mut bob_acc = Account { user_name: String::from(“bob"), balance: bob_balance };
    ...
    { /* TRANSACTION 1: Restrict the scope to the block. */
    let mut transfer = Transaction { from: &mut alice_acc, to: &mut bob_acc, amount: 10 };
    transfer.execute();
    }
    check_accounts(vec![&alice_acc, &bob_acc]);
    /* TRANSACTION 2: Restricted scope because Transaction is not assigned. */
    transfer(&mut alice_acc, &mut bob_acc, 10);
    check_accounts(vec![&alice_acc, &bob_acc]);
    }

    View full-size slide

  63. bank API
    fn main() {
    let mut alice_acc = Account { user_name: String::from("alice"), balance: alice_balance };
    let mut bob_acc = Account { user_name: String::from(“bob"), balance: bob_balance };
    ...
    { /* TRANSACTION 1: Restrict the scope to the block. */
    let mut transfer = Transaction { from: &mut alice_acc, to: &mut bob_acc, amount: 10 };
    transfer.execute();
    }
    check_accounts(vec![&alice_acc, &bob_acc]);
    /* TRANSACTION 2: Restricted scope because Transaction is not assigned. */
    transfer(&mut alice_acc, &mut bob_acc, 10);
    check_accounts(vec![&alice_acc, &bob_acc]);
    }

    View full-size slide

  64. bank API
    fn main() {
    let mut alice_acc = Account { user_name: String::from("alice"), balance: alice_balance };
    let mut bob_acc = Account { user_name: String::from(“bob"), balance: bob_balance };
    ...
    { /* TRANSACTION 1: Restrict the scope to the block. */
    let mut transfer = Transaction { from: &mut alice_acc, to: &mut bob_acc, amount: 10 };
    transfer.execute();
    }
    check_accounts(vec![&alice_acc, &bob_acc]);
    /* TRANSACTION 2: Restricted scope because Transaction is not assigned. */
    transfer(&mut alice_acc, &mut bob_acc, 10);
    check_accounts(vec![&alice_acc, &bob_acc]);
    }

    View full-size slide

  65. productivity tips (extra)

    View full-size slide

  66. tools to becoming productive
    • compiler driven development (CDD)
    • cargo check is faster than cargo run
    • cargo-watch (cargo check on file change)
    • clippy (linter)
    • cargo fmt (formatter)

    View full-size slide

  67. references and thanks
    • coworkers at iHeart Radio
    • the Rust community
    • Splash photo by Andrew Branch on Unsplash
    • https://rustbyexample.com/
    • https://doc.rust-lang.org/stable/book/first-edition/
    • https://doc.rust-lang.org/stable/book/second-edition/
    • https://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap
    • https://stackoverflow.com/questions/373419/whats-the-difference-between-passing-by-reference-vs-passing-by-value
    • https://stackoverflow.com/questions/24253344/move-vs-copy-in-rust?answertab=active#tab-top
    • https://blog.rust-lang.org/2017/03/16/Rust-1.16.html

    View full-size slide

  68. Ownership in Rust:
    the core principle for API design
    toidiu.com
    github.com/toidiu
    github.com/toidiu/talk_rust_ownership

    View full-size slide