Rust¶
Notes are of https://doc.rust-lang.org/book/
Quick Links¶
Cargo¶
cargo run
Compiles then runs code
cargo build
Compiles code
--release
enables all optimizations
cargo check
Quickly determines if the code will compile.
Variables¶
By default all variables are immutable, they can be made mutable by the mut
keyword. Variables can be shadowed by using the let
keyword. let
also
allows for the variable’s type to be overidden.
A scalar type represents a single value. Rust has four primary scalar types: integers, floating-point numbers, Booleans, and characters. You may recognize these from other programming languages. Let’s jump into how they work in Rust.
example of 32 bit¶
u32
i32
f32
Chars are unicode!
In summary:
const: An expression which can be evaluated where necessary
static: (barring optimizations) a location in global memory
let: (barring optimizations) a location in stack memory
Box/Arc: a managed pointer to heap memory
tupples¶
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
}
Lists¶
let a: [i32; 5] = [1, 2, 3, 4, 5];
let a = [3; 5]; // [3, 3, 3, 3, 3]
Summary¶
const: An expression which can be evaluated where necessary static: (barring optimizations) a location in global memory let: (barring optimizations) a location in stack memory Box/Arc: a managed pointer to heap memory
Funtions¶
Declared with
fn
snake_case
pass by reference
fn another_function(x: i32, y: i32) {
println!("The value of x is: {}", x);
println!("The value of y is: {}", y);
}
an arrow is used for return type
fn plus_one(x: i32) -> i32 {
x + 1
}
Conditionals¶
Unlike languages such as Ruby and JavaScript, Rust will not automatically try to convert non-Boolean types to a Boolean. You must be explicit and always provide if with a Boolean as its condition.
fn main() {
let condition = true;
let number = if condition {
5
} else {
6
};
println!("The value of number is: {}", number);
}
Loops¶
loop
infinite loop, no conditionals
for
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a.iter() {
println!("the value is: {}", element);
}
}
fn main() {
for number in (1..4).rev() {
println!("{}!", number);
}
println!("LIFTOFF!!!");
}
while
infinite loop, conditional
Ownership¶
Ownership is Rust’s most unique feature, and it enables Rust to make memory safety guarantees without needing a garbage collector.
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.
see figure 4-4 for an example of moving
Clone for deep copy
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
Barrowing¶
THERE CAN ONLY BE ONE MUTABLE REFERENCE AT A TIME
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
As always, we can use curly brackets to create a new scope, allowing for multiple mutable references, just not simultaneous ones:
let mut s = String::from("hello");
{
let r1 = &mut s;
} // r1 goes out of scope here, so we can make a new reference with no problems.
et r2 = &mut s;
Mixing mutable and immutable references is also bad:
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
let r3 = &mut s; // BIG PROBLEM
println!("{}, {}, and {}", r1, r2, r3);
Scope is weird:
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
println!("{} and {}", r1, r2);
// r1 and r2 are no longer used after this point (they go out of scope)
let r3 = &mut s; // no problem
println!("{}", r3);
Copy types¶
Stack only data
All the integer types, such as u32.
The Boolean type, bool, with values true and false.
All the floating point types, such as f64.
The character type, char.
Tuples, if they only contain types that are also Copy. For example, (i32, i32) is Copy, but (i32, String) is not
Slice Type¶
https://doc.rust-lang.org/book/ch04-03-slices.html like python…
String != str
str == Slice of String
A more experienced Rustacean would write the signature shown in Listing 4-9
instead because it allows us to use the same function on both String values and
&str
values.
fn first_word(s: &str) -> &str {}
Structs¶
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
derived traits : https://doc.rust-lang.org/book/ch05-02-example-structs.html
Methods¶
kinda like python, uses self
You don’t impliment methods in struct
impl
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!(
"The area of the rectangle is {} square pixels.",
rect1.area()
);
}
Collections¶
Lists
Vectors
HashMaps
Enums¶
For the love of god, remember this
https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html
Some vs None¶
NULL but better
matching¶
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
Generics¶
litterally have no performance hits.
fn largest<T>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
Traits¶
Traits are similar to a feature often called interfaces in other languages, although with some differences. Think inhertable functions that multiple structs can impliment. Trait implementations can only done on a type only if either the trait or the type is local to the current crate.
Definition Example
pub trait Summary {
fn summarize(&self) -> String;
}
Implimenting a Trait on a Type
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
Default Implementations
Just like functional overriding in C++. Anything that is implimenting a trait can overide these default values
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
Note that it isn’t possible to call the default implementation from an overriding implementation of that same method.
Traits as parameters
This may be more powerful than generics! You can have funtions take in any type that implements a certain trait!
pub fn notify(item: impl Summary) {
println!("Breaking news! {}", item.summarize());
}
To force the type of multiple parameters to be the same generics are needed so that a single type is used for both parameters.
pub fn notify<T: Summary>(item1: T, item2: T) {
Binding Multiple Traits
See the book here
for the basic details. When dealing with multiple generics use the where
notation to make which generics use what traits.
fn some_function<T, U>(t: T, u: U) -> i32
where T: Display + Clone,
U: Clone + Debug
{ /* Function goes here */}
Lifetimes¶
The main aim of lifetimes is to prevent dangling references. See the lifetime docs for more info.
Error Propegation¶
panic!
¶
When the panic! macro executes, your program will print a failure message, unwind and clean up the stack, and then quit. It is only to be used when the situation is not recoverable.
Result
¶
unwrap
and expect
Propegation with ?
¶
The ?
operator placed after a Result. 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.
Macros, Shit Just Got Real¶
:S
Modules¶
by using super at the start of the path. This is like starting a filesystem
path with the ..
syntax
TODO: pub use
Automated testing¶
To change a function into a test function, add #[test]
on the line before
fn
. To run the test cargo test
. Benchmarking is avaliable
Librarys to Know about¶
File IO
std::fs::File;
Stdin
std::io
Crates.io¶
fun crates to know about.
Sartor Recommended Crates¶
failure - A single polymorphic error type and related utilities.
rand - Since std doesn’t come with random number generation.
- serde - Super super easy to read/write json/yaml/etc.
serde_json performance is insane You should expect in the ballpark of 500 to 1000 megabytes per second deserialization and 600 to 900 megabytes per second serialization, depending on the characteristics of your data.
serde_with good macros for serde
lazy_static - If you ever need global variables.
crossbeam_channel - Real channels for multi threading.
reqwest - Good HTTP library.
cute - python like list comprehensions
How to use clone¶
Misc.¶
String::split()
returns a non-mutatble iterator and cannot be used to
provide &mut str
types
FStrings! format!("{}: {}", self.username, self.content)