Rust
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)
- [reason]you need to request a certain amount of space(need to more time to find and the steps like below)
- 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
- stack
- 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 likestring_from
- The second
-
[Memory and allocation] why can
String
bemutated
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 toResource Acquisition is Initialization
inC++
- the scope over Rust call special func
-
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
- how much memory, in bytes, the contents of the
- capacity
- the total amount of memory, in bytes, that the
String
has received from the allocator
- the total amount of memory, in bytes, that the
- pointer
-
This group of data is stored on the stack. On the right is the memory on the heap that holds the contents
- 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
- 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
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 calledclone
- 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
- Rust has a feature for this concept, called
- the ownership of a variable follows the same pattern every time
- [passing a value to a function or assigning a value to a variable]
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 bemutable
- 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 ownershipslice
let you reference a contiguous sequence of elements in a collectionstring slice
is written as&str
- if we have a
String
we can pass a slice of the entireString
- this can make our API more general and useful
- string literals are string slices already
- if we have a
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 nothingmatch
how pattern matching in thematch
expression make ir easy to run different code for different values of an enumenums
can inside thestruct
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
- two major categories of errors
- 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
- [unwind] when a panic occurs, the program starts
- conclusion
- default mode of
panic!
isunwind
- can configure to
abort
withpanic='abort'
under appropriate[profile]
section of Cargo.toml- this mod can let the binary as small as possible
- default mode of
- there are two ways to
- 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
- execution
- 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
- checking the
- 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 expressionexpect
similar tounwrap
but 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”
- work in almost the same way as the match expressions we defined to handle the
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 namedparameter
-
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
- taking ownership
- 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
- In Rust, iterators are lazy
- using closures that capture their environment
filter
- comparing performance:
loops
vsiterators
- 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 theM: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
- the problem of different threads run one task
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!