Rust

9 minute read

Chapter 4

learn about Rust’s ownership system

  • Rust uses the ownership feature to replace the garbage collection which in other languages
    • The memory is managed through a system of ownership with a set of rules that the compiler checks at compile time

    • additional contents from chapter-15

      • at any given time, you can have either(but not both of) one mutable reference of any number of immutable references
      • references must always be valid
  • The stack and heap
    • stack
      • all data stored on the stack must have a known, fixed size
      • store values in order it gets them and removes the values in the opposite order(last in, first out)
    • heap
      • data with an unknown size at compile time or a size might change must be stored on the heap instead
      • the heap is less organized
        • [reason]you need to request a certain amount of space(need to more time to find and the steps like below)
          • [allocating]the memory allocator finds an empty spot in the heap that is big enough, marks it as being in use, and return a pointer(address of that location)
    • pushing values onto the stack is not considered allocating
      • [reason]because the pointer is a known, fixed size, you can store the pointer on the stack, but when you want the actual data, you must follow the pointer
    • pushing to the stack is faster than allocating on the heap
    • accessing data in the heap is slower than accessing data on the stack because you have to follow a pointer to get there
    • contemporary processors are faster if they jump around less in memory
  • when your code calls a function
    • the values passed into the function (including, potentially, pointers to data on the heap)
    • and the function’s local variables get pushed onto the stack
    • when the function is over, those values get popped off the stack
  • ownership addresses
    • keeping track of what parts of code are using what data on the heap,
    • minimizing the amount of duplicate data on the heap
    • cleaning up unused data on the heap
  • ownership rules
    • each value in Rust has a variable that’s called its owner
    • there can only be one owner at a time
    • when the owner goes out of scope, the value will be dropped
  • shallow copy
    • the concept of copying the pointer, length and capacity without copying the data probably sounds like make a shallow copy
  • variable scope
    • come into scope is valid
    • it remains valid until it goes out of scope(the variable popped off the stack)
  • Most internal data types in rust
    • the types covered previously are all stored on the stack and popped off the stack when their scope is over
  • how rust knowns when the data stored on the heap and when to clean up that data?

  • [String instead of a string literal] String type
    • The second String type is allocated on the heap and as such is able to store an amount of text that is unknown to us at compile time
    • :: double colon operator allow us to namespace this particular from function under the String type rather than using some sort of name like string_from
  • [Memory and allocation] why can String be mutated but literal cannot?

    • two types deal with memory

    • [stack]string literal hardcoded directly into the final executable
      • so it fast and efficient
      • but all this come from string literal’s immutability
    • [heap]in order to support a mutable, growable piece of text we need to allocate an amount of memory on tha heap, unknown at compile time to hold the contents, so means:
      • the memory must be requested from the memory allocator at runtime
      • return this memory to the allocator when we’re done with our String
        • the scope over Rust call special func drop
          • drop func familiar to Resource Acquisition is Initialization in C++

more complicated situations(we want to have multiple variables use the data we’ve allocated on the heap)

ways variables and data interact: move

  • A String is made up of three parts:
    • pointer
      • to the memory that holds the contents of the string
    • length
      • how much memory, in bytes, the contents of the String is currently using
    • capacity
      • the total amount of memory, in bytes, that the String has received from the allocator
  • This group of data is stored on the stack. On the right is the memory on the heap that holds the contents the String on stack

  • if we assign s1 to s2, the String data is copied, meaning we copy the pointer, the length, and the capacity that are on the stack
    • We do not copy the data on the heap that the pointer refers to we assign s1 to s2
  • if variables have been move(shallow copy:copying pointer,length,and capacity without copying the data)
  • but rust invalidates the first variable instead of being called a shallow copy,this is known as move
  • we can only free memory after the second variable out of scope
  • if first variable still usable after assignment, Rust won’t let annotate a type with the copy trait if the type, or any of its parts has implemented the Drop trait shallow copy

conclusion

Rust will never automatically create “deep” copies of your data(because that automatic copying can be assumed to be inexpensive in terms of runtime performance)

ways variables and data interact: clone

  • if we want to deeply copy the heap data of the String, not just the stack data, we can called clone
  • deeply copy

stack-only data:copy

* if all data stored on stack, there is no difference between deep and shallow copying here
* Rust has a special annotation called the `Copy` trait that we can place on types like integers that are stored on the stack
  * If a type implements the `Copy` trait, an older variable is still usable after assignment
  * if we add the `Copy` annotation to the type(like integers), we’ll get a compile-time error
* Rust won’t let us annotate a type with the `Copy` trait if the type, or any of its parts, has implemented the Drop trait
* almost all internal type all implement `Copy`
  * all the integer types
  * the boolean type
  * floating point types
  * character type
  * tuple(only contain types that also implement `Copy`)
    * (i32,i32) implement `Copy`, but (i32,String) does not
  • [transfer ownership]
    • [passing a value to a function or assigning a value to a variable]

      ownership and functions

      • passing a variable to a function will move or copy,just as assignment does
      • passing parameters into the function and is no longer valid here
        • but if it is a internal type below, it can be valid and okay to still use afterward
        • if not the variables were removed, nothing special happens(like goes out scope and need to be drop)
      fn main() {
      let s = String::from("hello");  // s comes into scope
      
      takes_ownership(s);             // s's value moves into the function...
                                      // ... and so is no longer valid here
      
      let x = 5;                      // x comes into scope
      
      makes_copy(x);                  // x would move into the function,
                                      // but i32 is Copy, so it's okay to still
                                      // use x afterward
      
      } // Here, x goes out of scope, then s. But because s's value was moved, nothing
        // special happens.
      
      fn takes_ownership(some_string: String) { // some_string comes into scope
          println!("{}", some_string);
      } // Here, some_string goes out of scope and `drop` is called. The backing
        // memory is freed.
      
      fn makes_copy(some_integer: i32) { // some_integer comes into scope
          println!("{}", some_integer);
      } // Here, some_integer goes out of scope. Nothing special happens.
      
    • [Returning values can also transfer ownership]

      return values and scope

      • the ownership of a variable follows the same pattern every time
        • assigning a value to another variable moves it
        • when a variable that includes data on the heap goes out of scope the value will be cleaned up by drop
        • unless the data has been moved to be owned by another variable
      • What if we want to let a function use a value but not take ownership?
        • It’s quite annoying that anything we pass in also needs to be passed back.
      • If we want to use it again or return multiple values using a tuple?
        • Rust has a feature for this concept, called references
  • reference and borrowing
    • & references(The &s1 syntax lets us create a reference that refers to the value of s1 but does not own it)
    • the opposite of referencing by using & is dereferencing, which is accomplished with the dereference operator, *
    • we call having references as function parameters borrowing
    • we need to declare the variable refer can be mutable
    • we can only have one mutable reference to a particular piece of data in a particular scope

    data race

    Rust can prevent data races at compile time due to only one mutable reference to a particular piece of data in a particular scope

    • Two or more pointers access the same data at the same time
    • At least one of the pointers is being used write to the data
    • There’s no mechanism being used to synchronize access to the data

    new scope

    This should be known by everyone

    • As always, we can use curly brackets to create a new scope, allowing for multiple mutable references

    dangling references

    In languages with pointers, it’s easy to erroneously create a dangling pointer

    • dangling pointer
      • a point reference a location in memory that may have been given to someone else freeing some memory while preserving a pointer to that memory
    • In Rust the compiler guarantees that references will never be dangling references
      • the compiler will ensure that the data will not go out of scope before the reference to the data does
  • The slice type
    • slice type does not have ownership
    • slice let you reference a contiguous sequence of elements in a collection
    • string slice is written as &str
      • if we have a String we can pass a slice of the entire String
        • this can make our API more general and useful
      • string literals are string slices already
    • slice type &[i32] works the same way as string slices do
  • Conclusion

The concepts of ownership, borrowing, slices ensure memory safe at compile time, and owner of data automatically clean up that data when the owner goes out of scope, this let us do not write and debug extra code to get this control

Chapter 6

covers enums, match expressions, and the if let control flow construct You’ll use structs and enums to make custom types in Rust

Define Enum

  • enumerating its possible variants
  • Option which value can be either something or nothing
  • match how pattern matching in the match expression make ir easy to run different code for different values of an enum
  • enums can inside the struct as a multiple variant of special type or putting data directly into each enum variant
  • each variant can have different types and amounts of associated data
  • Rust does not have nulls
  • Option<T> support

The match Control Flow Operator

  • support None and other situation

The _ placeholder

  • _ is default operator can match any value

Concise Control Flow with if let

Chapter 9

explores Rust’s error-handling philosophy and techniques

  • Rust doesn’t have exceptions
    • two major categories of errors
      • recoverable
      • unrecoverable
  • unrecoverable errors with panic
    • execution panic! macro and unwind and clean up the stack, and then quit
      • there are two ways to panic in Rust
        • [unwind] when a panic occurs, the program starts unwinding that means Rust walks back up the stack and cleans up the data from each function it encounters
        • [immediately-abort] ends the program without cleaning up
          • this mean that the program was using will then need to be cleaned up by the operating system
      • conclusion
        • default mode of panic! is unwind
        • can configure to abort with panic='abort' under appropriate [profile] section of Cargo.toml
          • this mod can let the binary as small as possible
    • buffer overread
      • In C, attempting to read beyond the end of a data structure is undefined behavior. you might get whatever is at the location in memory that would correspond to that element in the data structure, even though the memory doesn’t belong to that structure
      • this can lead to security vulnerabilities
      • we can set the RUST_BACKTRACE environment variable to get a backtrace of exactly what happened to cause the error
  • recoverable errors with result
    • there have enum with generic type parameters of Result
    • how to know the function will return a Result
      • checking the standard library API documentation
      • ask the compiler
        • give a variant a type annotation that we know is not the return type of the function, and the compiler will tell use the types don’t match
  • shortcuts for Panic on error:unwrap and expect
    • match can be a bot verbose and doesn’t always communicate intent well
    • so there includes many helper methods defined on it to do various tasks
      • unwrap is implemented just like the match expression
      • expect similar to unwrapbut providing good error message can convey your intent and make tracking down the source of a panic easier
  • shortcut for propagating errors: the ? operator
    • work in almost the same way as the match expressions we defined to handle the Result value
    • Box<dyn Error> to mean “any kind of error”

Chapter 12

we’ll build our own implementation of a subset of functionality from the grep command line tool that searches for text within files

Chapter 13

explores closures and iterators: features of Rust that come from functional programming languages

  • |parameter| means which we specify the parameters to the closure
    • this syntax was chosen because of its similarity to closure definitions in Smalltalk and Ruby
    • this means that the closure has one parameter named parameter
  • all closures implement at least one of the traits: Fn, FnMut, FnOnce

  • closures can capture values from their environment in three ways, these ways directly map to the three ways a function can take a parameter
    • taking ownership
      • FnOnce consumers the variables it captures from its enclosing scope, known as the closures’s environment
        • the closure must take ownership of there variables and move them into the closure when it is defined
        • the Once part of the name represents the fact that the closure can not take ownership of the same variables more than once, so it can be called only once
    • borrowing mutably
      • FnMut can change the environment because it mutably borrows values
    • borrowing immutably
      • Fn borrows values from the environment immutably
  • Processing a series of items with iterators
    • In Rust, iterators are lazy
      • meaning they have no effect until you call methods that consume the iterator to use it up
  • using closures that capture their environment
    • filter
  • comparing performance: loops vs iterators
    • principle: what you do not use, you do not pay for. And further: what you do use, you couldn’t hand code any better
    • zero-cost abstractions means no additional runtime overhead

Chapter 14

we’ll examine Cargo in more depth and talk about best practices for sharing your libraries with others

Chapter 16 Fearless Concurrency

we’ll walk through different models of concurrent programming and talk about how Rust helps you to program in multiple threads fearlessly

  • using threads to run code simultaneously

    • the problem of different threads run one task
      • Race conditions, threads are accessing data or resources in an inconsistent order
      • Deadlocks, two threads waiting for each other
      • Bugs

      • the threads of programming languages provide are known as green threads, and languages that ue these green threads will execute them in the context of a different number of operating system threads, called the M:N model

      • the Rust standard library only provides an implementation of 1:1 threading
        • M:N model requires a larger language runtime to manage threads
        • Rust is such a low-level language
      • by runtime we mean code that is included by the language in every binary

      • every non-assembly language will have some amount of runtime code

      • colloquially when people say a language has “no runtime,” they often mean small runtime
        • smaller runtimes have fewer features but have advantage of resulting in smaller binaries, which make it easier to combine the language with other language in more contexts
      • many languages are okay with increasing the runtime size in exchange for more features, Rust needs to have nearly no runtime and cannot compromise on being able to call into C to maintain performance
    • Creating a New Thread with spawn



Chapter 17

looks at how Rust idioms compare to object-oriented programming principles you might be familiar with

Chapter 18

is a reference on patterns and pattern matching, which are powerful ways of expressing ideas throughout Rust programs

In Chapter 20

we’ll complete a project in which we’ll implement a low-level multithreaded web server!