-
The default type is
f64
because on modern CPUs it’s roughly the same speed asf32
but is capable of more precision. -
Declaring arrays:
let a: [i32; 5] = [1, 2, 3, 4, 5]
; -
isize
andusize
types depend on the kind of computer your program is running on: 64 bits if you’re on a 64-bit architecture and 32 bits if you’re on a 32-bit architecture. -
Unlike languages such as Ruby and JavaScript, Rust will not automatically try to convert non-Boolean types to a Boolean.
-
Rust does not have nulls, but it has
Option
:fn plus_one(x: Option<i32>) -> Option<i32> { match x { None => None, Some(i) => Some(i + 1), } }
-
Return value of the function is synonymous with the value of the final expression in the block of the body of a function. You can return early from a function by using the
return
keyword and specifying a value, but most functions return the last expression implicitly. -
If in a let statement:
let number = if condition { 5 } else { 6 };
-
Returning from loops:
let result = loop { counter += 1; if counter == 10 { break counter * 2; } };
-
if let
is syntax sugar for a match that runs code when the value matches one pattern and then ignores all other values:if let Coin::Quarter(state) = coin { println!("State quarter from {:?}!", state); } else { count += 1; }
- Stack & Heap:
- All data stored on the stack must have a known, fixed size.
- Data with an unknown size (like
String
) at compile time or a size that might change must be stored on the heap instead. - The heap is less organized: when you put data on the heap, you request a certain amount of space.
- The memory allocator finds an empty spot in the heap that is big enough, marks it as being in use, and returns a pointer, which is the address of that location. This process is called allocating on the heap and is sometimes abbreviated as just allocating.
- Pushing values onto the stack is not considered allocating. 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 because the allocator never has to search for a place to store new data; that location is always at the top of the stack. Comparatively, allocating space on the heap requires more work, because the allocator must first find a big enough space to hold the data and then perform bookkeeping to prepare for the next allocation.
- 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.
let s = String::from("hello");
- The double colon (
::
) is an operator that allows us to namespace this particular from function under the String type rather than using some sort of name like string_from.
- The double colon (
- When a variable goes out of scope, Rust calls a special function for us. This function is called
drop
, and it’s where the author of String can put the code to return the memory. Rust calls drop automatically at the closing curly bracket.- In C++, this pattern of deallocating resources at the end of an item’s lifetime is sometimes called Resource Acquisition Is Initialization (RAII).
- Rust will never automatically create “deep” copies of your data. Therefore, any automatic copying can be assumed to be inexpensive in terms of runtime performance.
- If we do want to deeply copy the heap data of the String, not just the stack data, we can use a common method called
clone
. - If a type implements the Copy trait, an older variable is still usable after assignment.
- Rust won’t let us annotate a type with the
Copy
trait if the type, or any of its parts, has implemented theDrop
trait (e.g. Stack only types can be copied).
- Rust won’t let us annotate a type with the
- Passing a value to a function & returning a value from a function transfers ownership.
- To pass a value without transfering ownership, we can use references.
- Having references as function parameters is called borrowing.
- Just as variables are immutable by default, so are references.
- You can mark references as mutable, just like variables.
- You can have only one mutable reference to a particular piece of data in a particular scope; this prevents data races.
- Same goes for combining mutable and immutable references.
- String slice:
let hello = &s[..5];
-
The entire instance must be mutable; Rust doesn’t allow us to mark only certain fields as mutable.
-
Field init shorthand:
fn build_user(email: String, username: String) -> User { User { email, username, active: true, sign_in_count: 1, } }
-
Struct update syntax:
let user2 = User { email: String::from("another@example.com"), username: String::from("anotherusername567"), ..user1 };
-
Tuple structs:
struct Color(i32, i32, i32);
-
Putting the specifier
:?
and:#?
inside the curly brackets tells println! we want to use an output format calledDebug
.- The Debug trait enables us to print our struct in a way that is useful for developers so we can see its value while we’re debugging our code.
- To do that, we add the annotation
#[derive(Debug)]
just before the struct definition.
-
Methods are different from functions in that they’re defined within the context of a struct.
-
Method syntax:
impl Rectangle { fn area(&self) -> u32 { self.width * self.height } }
-
If we wanted to change the instance that we’ve called the method on as part of what the method does, we’d use
&mut self
as the first parameter.- Having a method that takes ownership of the instance by using just
self
as the first parameter is rare; this technique is usually used when the method transforms self into something else and you want to prevent the caller from using the original instance after the transformation.
- Having a method that takes ownership of the instance by using just
-
We’re allowed to define functions within
impl
blocks that don’t takeself
as a parameter. These are called associated functions because they’re associated with the struct.- Associated functions are often used for constructors that will return a new instance of the struct.
- To call this associated function, we use the :: syntax with the struct name.
-
We're allowed to have multiple
impl
blocks.
-
Enums can have values:
enum IpAddr { V4(u8, u8), V6(String), }
-
We’re also able to define methods on enums, just like structs.
- To enable the code that calls our code to refer to that name as if it had been defined in that code’s scope, we can combine pub and use.
- We can use nested paths to bring the same items into scope in one line:
use std::{cmp::Ordering, io};
- Declare vector with initial values:
let v = vec![1, 2, 3];
- Getting values from vector:
let third: &i32 = &v[2];
: Will panic if out of bounds,v.get(2)
: Will return anOption
.
to_string
method is available on any type that implements the Display trait, like string literals do.let s = format!("{}-{}-{}", s1, s2, s3);
-
When the
panic!
macro executes, your program will print a failure message, unwind and clean up the stack, and then quit. -
Matching on
Result
:let f = match f { Ok(file) => file, Err(error) => panic!("Problem opening the file: {:?}", error), };
-
Matching with multiple errors:
let f = match f { Ok(file) => file, Err(error) => match error.kind() { ErrorKind::NotFound => match File::create("hello.txt") { Ok(fc) => fc, Err(e) => panic!("Problem creating the file: {:?}", e), }, other_error => { panic!("Problem opening the file: {:?}", other_error) } }, };
-
If the
Result
value is theOk
variant,unwrap
will return the value inside theOk
. If theResult
is theErr
variant,unwrap
will call thepanic!
macro for us. -
expect
, which is similar tounwrap
, lets us also choose thepanic!
error message. -
?
operator:- If the value of the Result is an Ok, the value inside the Ok will get returned from this expression, and the program will continue. If the value is an Err, the Err will be returned from the whole function as if we had used the return keyword so the error value gets propagated to the calling code.
- Used when propogating errors.
-
Defining a trait (interface):
pub trait Summary { fn summarize(&self) -> String; }
-
Implementing a trait:
impl Summary for NewsArticle { fn summarize(&self) -> String { format!("{}, by {} ({})", self.headline, self.author, self.location) } }
-
Passing the trait as a parameter:
pub fn notify(item: &impl Summary) { println!("Breaking news! {}", item.summarize()); }
-
Trait bounds:
-
Single:
fn largest<T: PartialOrd>(list: &[T]) -> T
, -
Multiple:
fn some_function<T, U>(t: &T, u: &U) -> i32 where T: Display + Clone, U: Clone + Debug { }
-
- Every reference in Rust has a lifetime, which is the scope for which that reference is valid.