Skip to content

Instantly share code, notes, and snippets.

@naironics
Forked from DarinM223/Concepts.md
Created September 10, 2024 04:54
Show Gist options
  • Save naironics/b51a1754de875012788e62b456cc5749 to your computer and use it in GitHub Desktop.
Save naironics/b51a1754de875012788e62b456cc5749 to your computer and use it in GitHub Desktop.

Revisions

  1. @DarinM223 DarinM223 revised this gist Sep 3, 2016. 1 changed file with 15 additions and 19 deletions.
    34 changes: 15 additions & 19 deletions Concepts.md
    Original file line number Diff line number Diff line change
    @@ -93,15 +93,13 @@ impl<T> Stack<T> {

    pub fn pop(&mut self) -> Option<T> {
    let head = mem::replace(&mut self.head, None); // retrieve the Node from the Option and and set self.head to be None
    match head {
    Some(old_head) => { // since there is only one owner of the Node now (self.head is None) you can move it out
    let old_head = *old_head;
    self.head = old_head.next; // self.head is now None so you can freely set it
    self.size -= 1;
    Some(old_head.data)
    },
    None => None
    }
    head.map(|old_head| {
    let old_head = *old_head;
    self.head = old_head.next;
    self.size -= 1;

    old_head.data
    })
    }
    }
    ```
    @@ -111,17 +109,15 @@ Because this case is especially common with Options, there is a take() method fo
    ```rust
    impl<T> Stack<T> {
    // ... other methods

    pub fn pop(&mut self) -> Option<T> {
    match self.head.take() { // retrieve the Node from the Option and set self.head to be None
    Some(old_head) => { // since there is only one owner of the Node now (self.head is None) you can move it out
    let old_head = *old_head;
    self.head = old_head.next; // self.head is None so you can freely set it
    self.size -= 1;
    Some(old_head.data)
    },
    None => None
    }
    self.head.take().map(|old_head| { // retrieve the Node from the Option and set self.head to be None
    let old_head = *old_head;
    self.head = old_head.next; // self.head is None so you can freely set it
    self.size -= 1;

    old_head.data
    })
    }
    }
    ```
  2. @DarinM223 DarinM223 revised this gist Jan 30, 2016. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions Concepts.md
    Original file line number Diff line number Diff line change
    @@ -24,13 +24,13 @@ Owned data is only automatically deleted when the owned variable no longer holds
    1. The owner variable goes out of scope and is destroyed
    2. The owner variable is set to another value, making the original data no longer accessable

    This simplifies the memory management problem a lot and eliminates the confusing 'which pointer should delete the data at the end?' problem that happens often in C or old C++.
    Ownership is not unique to Rust. Modern C++ generally recommends using 'unique_ptr', a smart pointer that also "owns" the data it wraps, over raw pointers.
    This simplifies the memory management problem and eliminates the confusing 'which pointer should delete the data at the end?' problem that happens often in C or old C++.
    Ownership is not unique to Rust. Modern C++ recommends using 'unique_ptr', a smart pointer that also "owns" the data it wraps, over raw pointers.

    When you declare a variable in Rust, the variable "owns" the data.

    ```rust
    let a = 2; // a "owns" 2
    let a = Box::new(2); // a "owns" a heap allocated integer
    ```

    When a variable owns something it can move it to other variables using assignment.
  3. @DarinM223 DarinM223 revised this gist Jan 30, 2016. 1 changed file with 36 additions and 9 deletions.
    45 changes: 36 additions & 9 deletions Concepts.md
    Original file line number Diff line number Diff line change
    @@ -15,9 +15,15 @@ Note: I don't cover Rust syntax here so if you cannot understand the syntax try

    ## Ownership

    The main concept of ownership is that if you own an item, you are the sole person in charge of destroying the item when you are done.
    The concept of ownership is that if you own an item, you are in charge of destroying the item when you are done.

    In Rust there can only be **one** owner of a piece of data at any time. This is a lot different from garbage collected languages or languages with raw pointers because instead of having multiple references to the same data, you often have to "juggle" around data so that only one variable owns the data at one time.

    Owned data is only automatically deleted when the owned variable no longer holds the data. This can happen when:

    1. The owner variable goes out of scope and is destroyed
    2. The owner variable is set to another value, making the original data no longer accessable

    So data is only automatically deleted when the owner of the data is destroyed. No other condition will cause the data to be freed.
    This simplifies the memory management problem a lot and eliminates the confusing 'which pointer should delete the data at the end?' problem that happens often in C or old C++.
    Ownership is not unique to Rust. Modern C++ generally recommends using 'unique_ptr', a smart pointer that also "owns" the data it wraps, over raw pointers.

    @@ -64,12 +70,12 @@ fn main() {
    }
    ```

    * If you have data embedded in something like an enum and you want to move it out without compile errors, look into the mem::replace function and for Options, look into the take() method. Both of these allow you to
    move out the value by setting the old owner to a dummy value (like None in the case of Option).
    Remember that there can only be one owner of data, so you can only move out values by clearing the existing owner.
    For example, here is a code snippet for a linked list:
    * A common theme when working with Rust is that when data is enclosed by a container like a Vec<Foo> or an Option<Blah>, if you want
    to get the data out you have to either manually clone the data or remove it from the container first. One problem is that sometimes you want to move out data out of a container without preventing the container variable from being accessed. To do that, you can use the mem::replace function, which "resets" the variable to a certain value and returns the owned data. Afterwards the original owned variable no longer owns the data and the variable set to the return value now owns the data. For example, here is a code snippet for a linked list:

    ```rust
    use std::mem;

    type Link<T> = Option<Box<Node<T>>>;

    struct Node<T> {
    @@ -82,10 +88,31 @@ pub struct Stack<T> {
    head: Link<T>,
    }

    impl Stack<T> {
    impl<T> Stack<T> {
    // ... other methods

    pub fn pop(&mut self) -> Option<T> {
    let head = mem::replace(&mut self.head, None); // retrieve the Node from the Option and and set self.head to be None
    match head {
    Some(old_head) => { // since there is only one owner of the Node now (self.head is None) you can move it out
    let old_head = *old_head;
    self.head = old_head.next; // self.head is now None so you can freely set it
    self.size -= 1;
    Some(old_head.data)
    },
    None => None
    }
    }
    }
    ```

    Because this case is especially common with Options, there is a take() method for Options that does the same thing but is less verbose:

    ```rust
    impl<T> Stack<T> {
    // ... other methods

    pub fn pop(&mut self) -> Option<T> {
    pub fn pop(&mut self) -> Option<T> {
    match self.head.take() { // retrieve the Node from the Option and set self.head to be None
    Some(old_head) => { // since there is only one owner of the Node now (self.head is None) you can move it out
    let old_head = *old_head;
    @@ -121,7 +148,7 @@ a mutable borrow when you really need it.

    To access the value in a borrowed reference you use the dereference operator '*'.

    When you borrow a variable, that variable becomes inaccessable until the borrowed variable
    When you borrow a variable, the owner variable becomes inaccessable until the borrowed variable
    is destroyed.

    ```rust
  4. @DarinM223 DarinM223 revised this gist Dec 5, 2015. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Concepts.md
    Original file line number Diff line number Diff line change
    @@ -67,7 +67,7 @@ fn main() {
    * If you have data embedded in something like an enum and you want to move it out without compile errors, look into the mem::replace function and for Options, look into the take() method. Both of these allow you to
    move out the value by setting the old owner to a dummy value (like None in the case of Option).
    Remember that there can only be one owner of data, so you can only move out values by clearing the existing owner.
    For example, here is a code snipped for a linked list:
    For example, here is a code snippet for a linked list:

    ```rust
    type Link<T> = Option<Box<Node<T>>>;
  5. @DarinM223 DarinM223 revised this gist Dec 5, 2015. 1 changed file with 36 additions and 3 deletions.
    39 changes: 36 additions & 3 deletions Concepts.md
    Original file line number Diff line number Diff line change
    @@ -31,7 +31,7 @@ When a variable owns something it can move it to other variables using assignmen
    After giving away its data, the old variable cannot access the value anymore, and the new variable is the new owner.

    ```rust
    let mufasa = "king"; // mufasa is the owner of "king"
    let mufasa = Box::new("king"); // mufasa is the owner of "king"
    let scar = mufasa; // the data "king" is moved from mufasa to scar

    println!("{}", scar); // scar is now the owner of "king"
    @@ -64,7 +64,40 @@ fn main() {
    }
    ```

    * It is easy to forget ownership concepts when working with more complex data
    * If you have data embedded in something like an enum and you want to move it out without compile errors, look into the mem::replace function and for Options, look into the take() method. Both of these allow you to
    move out the value by setting the old owner to a dummy value (like None in the case of Option).
    Remember that there can only be one owner of data, so you can only move out values by clearing the existing owner.
    For example, here is a code snipped for a linked list:

    ```rust
    type Link<T> = Option<Box<Node<T>>>;

    struct Node<T> {
    data: T,
    next: Link<T>,
    }

    pub struct Stack<T> {
    size: i32,
    head: Link<T>,
    }

    impl Stack<T> {
    // ... other methods

    pub fn pop(&mut self) -> Option<T> {
    match self.head.take() { // retrieve the Node from the Option and set self.head to be None
    Some(old_head) => { // since there is only one owner of the Node now (self.head is None) you can move it out
    let old_head = *old_head;
    self.head = old_head.next; // self.head is None so you can freely set it
    self.size -= 1;
    Some(old_head.data)
    },
    None => None
    }
    }
    }
    ```

    ## Borrowing

    @@ -156,7 +189,7 @@ fn hello(blah: &State) {
    }
    ```

    * Assigment is not the only thing that will attempt to move out of a borrowed reference. For example, pattern matching also tries to move the value.
    * Assignment is not the only thing that will attempt to move out of a borrowed reference. For example, pattern matching also tries to move the value.
    To prevent this, precede the match variable with 'ref' to borrow the match variable instead of moving

    ```rust
  6. @DarinM223 DarinM223 revised this gist Dec 5, 2015. 1 changed file with 20 additions and 22 deletions.
    42 changes: 20 additions & 22 deletions Concepts.md
    Original file line number Diff line number Diff line change
    @@ -15,24 +15,27 @@ Note: I don't cover Rust syntax here so if you cannot understand the syntax try

    ## Ownership

    When you own an item in real life, you are usually the sole owner of the item. When you know longer want an item you can either give it to another person
    or destroy it.
    The main concept of ownership is that if you own an item, you are the sole person in charge of destroying the item when you are done.

    Rust does something very similar. When you declare a variable in Rust, the variable "owns" the data.
    So data is only automatically deleted when the owner of the data is destroyed. No other condition will cause the data to be freed.
    This simplifies the memory management problem a lot and eliminates the confusing 'which pointer should delete the data at the end?' problem that happens often in C or old C++.
    Ownership is not unique to Rust. Modern C++ generally recommends using 'unique_ptr', a smart pointer that also "owns" the data it wraps, over raw pointers.

    When you declare a variable in Rust, the variable "owns" the data.

    ```rust
    let a = 2; // a "owns" 2
    ```

    When a variable owns something it can give it to other variables using assignment.
    After giving away your data, the old variable cannot access the value anymore, and the new variable is the new owner.
    When a variable owns something it can move it to other variables using assignment.
    After giving away its data, the old variable cannot access the value anymore, and the new variable is the new owner.

    ```rust
    let a = 2; // a "owns" 2
    let b = a; // a gives its data to b
    let mufasa = "king"; // mufasa is the owner of "king"
    let scar = mufasa; // the data "king" is moved from mufasa to scar

    println!("{}", b); // b now owns the data previously in a
    println!("{}", a); // ERROR: a no longer has anything
    println!("{}", scar); // scar is now the owner of "king"
    println!("{}", mufasa); // ERROR: mufasa can no longer be accessed
    ```

    At the end of the scope where the owner is created, the data is destroyed
    @@ -65,21 +68,16 @@ fn main() {

    ## Borrowing

    Even though you are the only person who owns an item you often don't want to do everything yourself.
    In the same sense, you might want to pass a value to a function without moving the value inside the function.
    The previous section highlighted a big flaw with ownership by itself.
    There are many cases where you want to manipulate data, without actually owning the data.
    For example you might want to pass a value to a function and still be able to call the owner variable outside the function.

    Rust allows you to do this using the concept of borrowing. Borrowing is just like what you think it is, it just allows
    another variable to temporarily borrow the data in your variable and gives it back when its done.
    For example, if you own a computer but don't know how to install a software package, you might temporarily give the computer to a friend who knows so that he can install it for you.

    So what happens with borrowing is that:

    1. You give the item to your friend (but you still own it)
    2. Your friend does stuff with the borrowed item
    3. Your friend gives back the item to you

    Rust allows you to have two types of borrows:
    * immutable borrows with '&' (I want to borrow your book but I promise not to scribble on it)
    * mutable borrows with '&mut' (I want to borrow your computer so I can install things/change stuff on your computer)
    * immutable borrows with '&' (you can read the value of the borrowed data, but you can't modify it)
    * mutable borrows with '&mut' (you can both read and modify the value of the borrowed data)

    You can either have:
    * A lot of immutable borrows
    @@ -95,7 +93,7 @@ is destroyed.

    ```rust
    let mut x = 5;
    let y = &mut x; // y says 'hey can I borrow your data' to x
    let y = &mut x; // y borrows the data from x
    println!("{}", x); // ERROR: x no longer has the data, y has it!
    ```

    @@ -105,7 +103,7 @@ When a borrowed variable is destroyed, it gives back the borrowed value back to
    let mut x = 5;

    {
    let y = &mut x; // y says 'hey can I borrow your data' to x
    let y = &mut x; // y borrows the data from x
    *y += 1; // y changes the borrowed data
    } // y gives back the data to x

  7. @DarinM223 DarinM223 revised this gist Dec 4, 2015. 1 changed file with 46 additions and 7 deletions.
    53 changes: 46 additions & 7 deletions Concepts.md
    Original file line number Diff line number Diff line change
    @@ -8,8 +8,10 @@ There are three main concepts with Rust:
    3. Lifetimes (all data keeps track of when it will be destroyed)

    These are fairly simple concepts, but they are often counter-intuitive to concepts in other languages, so I wanted to give a shot at
    explaining these concepts in more detail. Because Rust is not the only language planned to use similar concepts
    (C++ is planning to include a similar ownership model), learning these concepts will not just help write better Rust code but better code in general.
    explaining these concepts in more detail. Because Rust is not the only language that uses these concepts
    (you can do unique_ptr in C++ for example), learning these concepts will not just help you write better Rust code but better code in general.

    Note: I don't cover Rust syntax here so if you cannot understand the syntax try to skim through the short online Rust book first https://doc.rust-lang.org/stable/book/

    ## Ownership

    @@ -77,8 +79,16 @@ So what happens with borrowing is that:

    Rust allows you to have two types of borrows:
    * immutable borrows with '&' (I want to borrow your book but I promise not to scribble on it)
    * mutable borrows with '&mut' (I want to borrow your computer so I can install things/change stuff on your computer)

    You can either have:
    * A lot of immutable borrows
    * Only one mutable borrow

    in a scope for a piece of data at any given time. So you should try to do immutable borrows most of the time and only do
    a mutable borrow when you really need it.

    To access the value in a reference you use the dereference operator '*'.
    To access the value in a borrowed reference you use the dereference operator '*'.

    When you borrow a variable, that variable becomes inaccessable until the borrowed variable
    is destroyed.
    @@ -102,10 +112,16 @@ let mut x = 5;
    println!("{}", x); // x has the data back again (and its changed to 6)
    ```

    Rust also has some extra rules for borrowing.
    You can have either one of these or the other in the current scope, but not both:
    * You can have lots of read-only borrows to a variable
    * You can have one read-write borrow to a variable
    Because of this, the owner has to live longer than the borrower

    ```rust
    let mut x: &i32;

    {
    let y = 3;
    x = &y; // ERROR: x lives longer than y!
    } // y gets destroyed here! What would happen to x if the Rust compiler didn't prevent this from happening?
    ```

    Tips & Tricks:
    * Function parameters end up mostly being borrowed references because otherwise the value will be moved inside the function
    @@ -142,6 +158,29 @@ fn hello(blah: &State) {
    }
    ```

    * Assigment is not the only thing that will attempt to move out of a borrowed reference. For example, pattern matching also tries to move the value.
    To prevent this, precede the match variable with 'ref' to borrow the match variable instead of moving

    ```rust
    enum State {
    Hello(String),
    Bye,
    }

    fn hello(blah: &State) {
    match *blah {
    State::Hello(s) => println!("Hello, {}", s), // ERROR: moves data inside blah (the string) into the variable s
    State::Bye => println!("Bye!"),
    }

    // do this instead
    match *blah {
    State::Hello(ref s) => println!("Hello, {}", s), // borrow the string data inside blah
    State::Bye => println!("Bye!"),
    }
    }
    ```

    ## Lifetimes

    TODO: write this later
  8. @DarinM223 DarinM223 revised this gist Dec 4, 2015. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions Concepts.md
    Original file line number Diff line number Diff line change
    @@ -8,8 +8,8 @@ There are three main concepts with Rust:
    3. Lifetimes (all data keeps track of when it will be destroyed)

    These are fairly simple concepts, but they are often counter-intuitive to concepts in other languages, so I wanted to give a shot at
    explaining these concepts in more detail. Because C++ is also starting to adopt them, there is definitely a huge benefit to learning
    these concepts
    explaining these concepts in more detail. Because Rust is not the only language planned to use similar concepts
    (C++ is planning to include a similar ownership model), learning these concepts will not just help write better Rust code but better code in general.

    ## Ownership

  9. @DarinM223 DarinM223 revised this gist Dec 4, 2015. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions Concepts.md
    Original file line number Diff line number Diff line change
    @@ -2,6 +2,7 @@ My explanation of the main concepts in Rust
    ===========================================

    There are three main concepts with Rust:

    1. Ownership (only one variable "owns" the data at one time, and the owner is in charge of deallocating)
    2. Borrowing (you can borrow a reference to an owned variable)
    3. Lifetimes (all data keeps track of when it will be destroyed)
  10. @DarinM223 DarinM223 renamed this gist Dec 4, 2015. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  11. @DarinM223 DarinM223 created this gist Dec 4, 2015.
    146 changes: 146 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,146 @@
    My explanation of the main concepts in Rust
    ===========================================

    There are three main concepts with Rust:
    1. Ownership (only one variable "owns" the data at one time, and the owner is in charge of deallocating)
    2. Borrowing (you can borrow a reference to an owned variable)
    3. Lifetimes (all data keeps track of when it will be destroyed)

    These are fairly simple concepts, but they are often counter-intuitive to concepts in other languages, so I wanted to give a shot at
    explaining these concepts in more detail. Because C++ is also starting to adopt them, there is definitely a huge benefit to learning
    these concepts

    ## Ownership

    When you own an item in real life, you are usually the sole owner of the item. When you know longer want an item you can either give it to another person
    or destroy it.

    Rust does something very similar. When you declare a variable in Rust, the variable "owns" the data.

    ```rust
    let a = 2; // a "owns" 2
    ```

    When a variable owns something it can give it to other variables using assignment.
    After giving away your data, the old variable cannot access the value anymore, and the new variable is the new owner.

    ```rust
    let a = 2; // a "owns" 2
    let b = a; // a gives its data to b

    println!("{}", b); // b now owns the data previously in a
    println!("{}", a); // ERROR: a no longer has anything
    ```

    At the end of the scope where the owner is created, the data is destroyed

    ```rust
    {
    let a = Box::new(2); // a owns a heap allocated integer
    } // a's data deallocated here
    ```

    Tips & Tricks:

    * Passing values into functions will "move" the data into the function variable. Once that happens the original variable
    cannot be accessed. This seems pretty restrictive, which is why the next topic tries to solve this.

    ```rust
    fn hello(a: Box<i32>) {
    println("{:?}", a); // prints "2"
    }

    fn main() {
    let b = Box::new(2);
    hello(b); // moves b into hello's a parameter

    b; // ERROR: cannot access b after it gave its value to a
    }
    ```

    * It is easy to forget ownership concepts when working with more complex data

    ## Borrowing

    Even though you are the only person who owns an item you often don't want to do everything yourself.
    In the same sense, you might want to pass a value to a function without moving the value inside the function.
    Rust allows you to do this using the concept of borrowing. Borrowing is just like what you think it is, it just allows
    another variable to temporarily borrow the data in your variable and gives it back when its done.
    For example, if you own a computer but don't know how to install a software package, you might temporarily give the computer to a friend who knows so that he can install it for you.

    So what happens with borrowing is that:

    1. You give the item to your friend (but you still own it)
    2. Your friend does stuff with the borrowed item
    3. Your friend gives back the item to you

    Rust allows you to have two types of borrows:
    * immutable borrows with '&' (I want to borrow your book but I promise not to scribble on it)

    To access the value in a reference you use the dereference operator '*'.

    When you borrow a variable, that variable becomes inaccessable until the borrowed variable
    is destroyed.

    ```rust
    let mut x = 5;
    let y = &mut x; // y says 'hey can I borrow your data' to x
    println!("{}", x); // ERROR: x no longer has the data, y has it!
    ```

    When a borrowed variable is destroyed, it gives back the borrowed value back to the owner.

    ```rust
    let mut x = 5;

    {
    let y = &mut x; // y says 'hey can I borrow your data' to x
    *y += 1; // y changes the borrowed data
    } // y gives back the data to x

    println!("{}", x); // x has the data back again (and its changed to 6)
    ```

    Rust also has some extra rules for borrowing.
    You can have either one of these or the other in the current scope, but not both:
    * You can have lots of read-only borrows to a variable
    * You can have one read-write borrow to a variable

    Tips & Tricks:
    * Function parameters end up mostly being borrowed references because otherwise the value will be moved inside the function
    * Function return values should not be references to local variables and Rust will not let you do this.
    If you returned a pointer to a local value in C you could either end up with corrupted data or the return value won't know when to deallocate its data
    which is bad either way you look at it
    * Don't worry about dereferencing to "read" or "write" (depending on & or &mut) the value of a borrowed reference

    ```rust
    enum State {
    Hello,
    Bye,
    }
    fn hello(blah: &State, foo: &mut i32) {
    match *blah { // you are only reading an immutable reference so its fine
    State::Hello => println!("Hello!"),
    State::Bye => println!("Bye!"),
    }

    *foo += 1; // you are only writing to a mutable reference so its fine
    }
    ```

    * Do worry about assigning dereferenced references to variables, because that will instead try to move the data to the new variable

    ```rust
    enum State {
    Hello,
    Bye,
    }
    fn hello(blah: &State) {
    let thief = *blah; // ERROR: blah can't give a borrowed item to thief!
    // thief is going to take the value without giving it back to the original owner
    }
    ```

    ## Lifetimes

    TODO: write this later