mirror of
https://gitlab.com/djdietrick/docs
synced 2026-05-03 03:50:54 -04:00
More rust updates
This commit is contained in:
136
docs/rust/basics/collections.md
Normal file
136
docs/rust/basics/collections.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# Collections
|
||||
|
||||
## Vector
|
||||
|
||||
Vectors allow you to store multiple values next to each other in memory. Vectors can only contain a single type. Enums of the same type count, so you can store multiple kinds of values using an enum. You can create vectors with the `new` class function, or with the `vec!` macro.
|
||||
|
||||
```rust
|
||||
let v: Vec<i32> = Vec::new();
|
||||
|
||||
let v = vec![1, 2, 3];
|
||||
v.push(5);
|
||||
v.push(6);
|
||||
v.push(7);
|
||||
v.push(8);
|
||||
|
||||
enum SpreadsheetCell {
|
||||
Int(i32),
|
||||
Float(f64),
|
||||
Text(String),
|
||||
}
|
||||
|
||||
let row = vec![
|
||||
SpreadsheetCell::Int(3),
|
||||
SpreadsheetCell::Text(String::from("blue")),
|
||||
SpreadsheetCell::Float(10.12),
|
||||
];
|
||||
```
|
||||
|
||||
Values can be read from a vector with brackets or the `get` method. Retrieving from a vector gives you a reference to the element. If you try to access a vector with brackets that is out of the range, then the code will panic.
|
||||
|
||||
```rust
|
||||
let v = vec![1, 2, 3, 4, 5];
|
||||
|
||||
let third: &i32 = &v[2];
|
||||
|
||||
let third: Option<&i32> = v.get(2);
|
||||
```
|
||||
|
||||
To iterate over a vector, we use the `for in` syntax. You can also iterate over a mutable vector and mutate the elements.
|
||||
|
||||
```rust
|
||||
let v = vec![100, 32, 57];
|
||||
for i in &v {
|
||||
println!("{}", i);
|
||||
}
|
||||
|
||||
let mut v = vec![100, 32, 57];
|
||||
for i in &mut v {
|
||||
*i += 50;
|
||||
}
|
||||
```
|
||||
|
||||
## Strings
|
||||
|
||||
### `String`
|
||||
|
||||
The `String` type is part of the Rust standard library instead of imbedded in the core language. It is owned, mutable, growable, and UTF-8 encoded. A `String` is actually just a wrapper of the `Vec<T>` type, so a lot of the same methods apply. You can append to a mutable string with `push_str` which takes a string slice, so it does not take ownership of the item being passed in.
|
||||
|
||||
```rust
|
||||
let mut s = String::new();
|
||||
let s2 = "bar";
|
||||
s1.push_str(s2);
|
||||
println!("s2 is {}", s2);
|
||||
|
||||
// From string literal
|
||||
let data = "initial contents";
|
||||
let s = data.to_string();
|
||||
let s = "initial contents".to_string();
|
||||
let s = String::from("initial contents");
|
||||
```
|
||||
|
||||
Concatenating strings can be done with either the `+` operator or the `format!` macro. You can see that `s2` is still usable after the concatenation, this is because the add function has the definition `fn add(self, s: &str) -> String`, meaning the second argument is a string slice and therefore not transfered (`&String` can be transformed to a `&str` automatically). `format!` takes strings and returns another `String` and doesn't take ownership of any of the arguments.
|
||||
|
||||
```rust
|
||||
let s1 = String::from("Hello, ");
|
||||
let s2 = String::from("world!");
|
||||
let s3 = s1 + &s2; // note s1 has been moved here and can no longer be used
|
||||
|
||||
let s1 = String::from("tic");
|
||||
let s2 = String::from("tac");
|
||||
let s3 = String::from("toe");
|
||||
|
||||
let s = format!("{}-{}-{}", s1, s2, s3);
|
||||
```
|
||||
|
||||
You can iterate over strings by using a `for in` loop and the `chars()` function.
|
||||
|
||||
```rust
|
||||
for c in "З21".chars() {
|
||||
println!("{}", c);
|
||||
}
|
||||
```
|
||||
|
||||
### `&str` or String Slice
|
||||
|
||||
A string slice is a reference to a part of a `String`. When writing functions that take a string reference, it is better to use `&str` because `&String` is automatically converted to a string slice if passed into a function that takes one.
|
||||
|
||||
```rust
|
||||
let s = String::from("hello world");
|
||||
let hello = &s[0..5];
|
||||
let world = &s[6..11];
|
||||
```
|
||||
|
||||
## Hash Maps
|
||||
|
||||
Stores a collection of key-value pairs. The keys must all be the same type, and the values must all be the same type. The `get` method returns an `Option<&V>`. For types that implement the `Copy` trait, they will be copied when inserted into the hash map. Otherwise, like strings, ownership will be transfered. If you insert a reference, the value the reference is pointing to must be valid for the lifetime of the hash map.
|
||||
|
||||
```rust
|
||||
use std::collections::HashMap;
|
||||
|
||||
let mut scores = HashMap::new();
|
||||
|
||||
scores.insert(String::from("Blue"), 10);
|
||||
scores.insert(String::from("Yellow"), 50);
|
||||
|
||||
let team_name = String::from("Blue");
|
||||
let score = scores.get(&team_name);
|
||||
|
||||
for (key, value) in &scores {
|
||||
println!("{}: {}", key, value);
|
||||
}
|
||||
|
||||
scores.insert(String::from("Blue"), 25); // Overwrites
|
||||
scores.entry(String::from("Blue")).or_insert(50); // Add if not present
|
||||
|
||||
// Update value based on previous value
|
||||
let text = "hello world wonderful world";
|
||||
|
||||
let mut map = HashMap::new();
|
||||
|
||||
for word in text.split_whitespace() {
|
||||
let count = map.entry(word).or_insert(0);
|
||||
*count += 1;
|
||||
}
|
||||
|
||||
```
|
||||
@@ -15,6 +15,13 @@ if num < 3 {
|
||||
|
||||
### With Options and Results
|
||||
|
||||
```rust
|
||||
let config_max = Some(3u8);
|
||||
if let Some(max) = config_max {
|
||||
println!("The maximum is configured to be {}", max);
|
||||
}
|
||||
```
|
||||
|
||||
## Loops
|
||||
|
||||
### Loop
|
||||
@@ -72,4 +79,38 @@ for number in (1..4).rev() {
|
||||
println!("{number}!");
|
||||
}
|
||||
println!("LIFTOFF!!!");
|
||||
```
|
||||
|
||||
## Match
|
||||
|
||||
```rust
|
||||
enum Coin {
|
||||
Penny,
|
||||
Nickel,
|
||||
Dime,
|
||||
Quarter(UsState),
|
||||
}
|
||||
|
||||
fn value_in_cents(coin: Coin) -> u8 {
|
||||
match coin {
|
||||
Coin::Penny => 1,
|
||||
Coin::Nickel => 5,
|
||||
Coin::Dime => 10,
|
||||
Coin::Quarter(state) => {
|
||||
println!("State quarter from {:?}!", state);
|
||||
25
|
||||
},
|
||||
_ => {
|
||||
println("Other!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn plus_one(x: Option<i32>) -> Option<i32> {
|
||||
match x {
|
||||
None => None,
|
||||
Some(i) => Some(i + 1),
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
162
docs/rust/basics/ownership.md
Normal file
162
docs/rust/basics/ownership.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# Ownership
|
||||
|
||||
Ownership in Rust is very strict when compared to other languages. This allows it to be memory safe without the need for a garbage collector. If any of the ownership rules are broken, the code will not compile. The ownership rules for Rust are:
|
||||
|
||||
- Each value in Rust has an owner.
|
||||
- There can only be one owner at a time.
|
||||
- When the owner goes out of scope, the value will be dropped.
|
||||
|
||||
## Stack vs Heap
|
||||
|
||||
The stack is last in, first out. All items on the stack must have a fixed size, so anything with unknown size at compile time goes on the heap. The stack is faster because you don't have to search for space to put the data. When you call a function, the values getting passed to the function and the function's local variables get pushed onto the stack, and get popped off when the function ends.
|
||||
|
||||
The heap is less organized. You have to look for a space to place the data, and when it does you receive a pointer to that data. This pointer is then stored on the stack because it is a fixed size. Allocating to the heap takes more work, and accessing data takes more time because you have to follow the pointer. Once the pointer goes out of scope, the memory on the heap is automatically freed.
|
||||
|
||||
## Transfering ownership
|
||||
|
||||
In Rust when you copy a pointer, the ownership is transfered to the new variable and the first variable is no longer valid. In order to do a deep copy of the data on the heap, you can use the common `clone` method. This doesn't apply to basic types with a known size however, because they are fixed size and stored on the stack they are cheap to copy.
|
||||
|
||||
```rust
|
||||
let s1 = String::from("hello");
|
||||
let s2 = s1;
|
||||
|
||||
println!("{}, world!", s1); // This will throw because ownership has been transfered to s2.
|
||||
|
||||
let s3 = String::from("hello");
|
||||
let s4 = s3.clone();
|
||||
|
||||
println!("s3 = {}, s4 = {}", s3, s4); // Works fine
|
||||
|
||||
let x = 5;
|
||||
let y = x;
|
||||
|
||||
println!("x = {}, y = {}", x, y); // Works fine
|
||||
```
|
||||
|
||||
Passing values into functions follow the same ownership transfering rules as assignment. Variables passed into a function are moved into the scope of the function and fall off the stack when the function ends. If a value is returned from a function, it is assigned to a variable in the scope of the function caller.
|
||||
|
||||
## References and Borrowing
|
||||
|
||||
A reference is like a pointer in that it's an address you can follow to get to a piece of data, except someone else is the owner of that data. A reference is guaranteed to point to a valid point for the lifetime of the reference. If you pass a reference to a variable into a function, that variable is still valid after the function call because only the reference, or the pointer to the pointer, goes out of scope. Since it does not own the data, the data remains valid. Not that you need to include the `&` when calling the function as well, acknowledging that you know this function takes a reference.
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let s1 = String::from("hello");
|
||||
|
||||
let len = calculate_length(&s1);
|
||||
|
||||
println!("The length of '{}' is {}.", s1, len);
|
||||
}
|
||||
|
||||
fn calculate_length(s: &String) -> usize {
|
||||
s.len()
|
||||
}
|
||||
```
|
||||
|
||||
### Mutable References
|
||||
|
||||
By default, reference are immutable. You can however declare mutable reference with `&mut`.
|
||||
```rust
|
||||
fn main() {
|
||||
let mut s = String::from("hello");
|
||||
|
||||
change(&mut s);
|
||||
|
||||
println!("{}", s);
|
||||
}
|
||||
|
||||
fn change(some_string: &mut String) {
|
||||
some_string.push_str(", world");
|
||||
}
|
||||
```
|
||||
|
||||
The one big restriction is that you can only define one mutable reference to a given value in the current scope. You can have multiple normal references to value however because they are read only by definition.
|
||||
|
||||
```rust
|
||||
let mut s = String::from("hello");
|
||||
|
||||
let r1 = &mut s;
|
||||
let r2 = &mut s;
|
||||
|
||||
println!("{}, {}", r1, r2); // Will error when used
|
||||
|
||||
let mut s2 = String::from("hello");
|
||||
|
||||
let r4 = &s2; // no problem
|
||||
let r5 = &s2; // no problem
|
||||
let r6 = &mut s2; // BIG PROBLEM
|
||||
|
||||
println!("{}, {}, and {}", r4, r5, r6); // Will eerror when used
|
||||
```
|
||||
|
||||
Edge case
|
||||
|
||||
```rust
|
||||
let mut s = String::from("hello");
|
||||
|
||||
let r1 = &s; // no problem
|
||||
let r2 = &s; // no problem
|
||||
println!("{} and {}", r1, r2);
|
||||
// variables r1 and r2 will not be used after this point
|
||||
|
||||
let r3 = &mut s; // no problem
|
||||
println!("{}", r3);
|
||||
```
|
||||
|
||||
## Lifetimes
|
||||
|
||||
Lifetime is the scope for which a given reference is valid. In the case where the lifetime is ambiguous, we must annotate the variable to let other pieces of our code know how long it will be alive for. Lifetimes are denoted with the `'` character, and can be anything as long it is all lowercase characters.
|
||||
|
||||
```rust
|
||||
&i32 // a reference
|
||||
&'a i32 // a reference with an explicit lifetime
|
||||
&'a mut i32 // a mutable reference with an explicit lifetime
|
||||
|
||||
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
|
||||
if x.len() > y.len() {
|
||||
x
|
||||
} else {
|
||||
y
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### In functions
|
||||
|
||||
When using in a function definition, you say that the string slice arguments will live for a lifetime `'a`, which is equal to the lifetime of the return value (because it is essentially one of the inputs). This lifetime is equal to the shortest of the two variables' lifetimes, meaning you cannot use the return string after any of the arguments have gone out of scope.
|
||||
|
||||
```rust
|
||||
let string1 = String::from("long string is long");
|
||||
let result;
|
||||
{
|
||||
let string2 = String::from("xyz");
|
||||
result = longest(string1.as_str(), string2.as_str());
|
||||
}
|
||||
println!("The longest string is {}", result); // This will throw because string2 is out of scope
|
||||
```
|
||||
|
||||
Lifefimes in functions only need to be defined when the input influences the output.
|
||||
|
||||
```rust
|
||||
fn longest<'a>(x: &'a str, y: &str) -> &'a str {
|
||||
x
|
||||
}
|
||||
```
|
||||
|
||||
### In structs
|
||||
|
||||
Similar to functions, member variables in structs may have a lifetime if they are a reference. Then, the struct must live for as long as the owner of the reference.
|
||||
|
||||
```rust
|
||||
struct ImportantExcerpt<'a> {
|
||||
part: &'a str,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let novel = String::from("Call me Ishmael. Some years ago...");
|
||||
let first_sentence = novel.split('.').next().expect("Could not find a '.'");
|
||||
let i = ImportantExcerpt {
|
||||
part: first_sentence,
|
||||
};
|
||||
}
|
||||
```
|
||||
@@ -1,2 +1,130 @@
|
||||
# Structs
|
||||
|
||||
## Basic Structs
|
||||
|
||||
Structs are similar to tuples in that they hold multiple values, however their properties are named. Struct fields are also mutable.
|
||||
|
||||
Note that you should always use `String` in structs because we want the data to live on the structs stack and die when the struct does. When possible, all data in the struct should belong to the struct, otherwise you will need to implement lifetimes.
|
||||
|
||||
```rust
|
||||
struct User {
|
||||
username: String,
|
||||
email: String,
|
||||
age: i32,
|
||||
active: bool
|
||||
}
|
||||
|
||||
let user1 = User {
|
||||
email: String::from("someone@example.com"),
|
||||
username: String::from("someusername123"),
|
||||
active: true,
|
||||
age: 29,
|
||||
};
|
||||
|
||||
fn build_user(email: String, username: String) -> User {
|
||||
User {
|
||||
email,
|
||||
username,
|
||||
active: true,
|
||||
sign_in_count: 1,
|
||||
}
|
||||
}
|
||||
|
||||
let user2 = User {
|
||||
email: String::from("another@example.com"),
|
||||
..user1
|
||||
};
|
||||
```
|
||||
|
||||
## Tuple Structs
|
||||
|
||||
Tuple stucts are structs that have unnamed fields and are only defined by the types. You would use this when you want the tuple to be a different type than other tuples, but naming the fields would be unnecessary.
|
||||
|
||||
```rust
|
||||
struct Color(i8,i8,i8);
|
||||
let _black = Color(0, 0, 0);
|
||||
```
|
||||
|
||||
## Unit Like Struct
|
||||
|
||||
These types of structs are used when you only want to apply traits to a type that doesn't require any data.
|
||||
|
||||
```rust
|
||||
struct Unit;
|
||||
```
|
||||
|
||||
## Generics
|
||||
|
||||
```rust
|
||||
struct Point<T> {
|
||||
x: T,
|
||||
y: T
|
||||
}
|
||||
|
||||
let _integer = Point { x: 5, y: 10 };
|
||||
let _float = Point { x: 1.0, y: 4.0 };
|
||||
```
|
||||
|
||||
# Methods
|
||||
|
||||
Methods are defined by implementing a struct with the `impl` keyword. Here the functions can accept a reference to `self` to have access to all of the instance variables of that class. You can have multiple `impl` blocks if you want.
|
||||
|
||||
```rust
|
||||
struct Rectangle {
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
impl Rectangle {
|
||||
fn area(&self) -> u32 {
|
||||
self.width * self.height
|
||||
}
|
||||
|
||||
fn new(w: i32, h: i32) -> Rectangle {
|
||||
Rectangle {
|
||||
width: w,
|
||||
height: h
|
||||
}
|
||||
}
|
||||
|
||||
fn square(size: u32) -> Self {
|
||||
Self {
|
||||
width: size,
|
||||
height: size
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Overloading Methods
|
||||
|
||||
```rust
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct Point {
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
|
||||
impl Add for Point {
|
||||
type Output = Point;
|
||||
|
||||
fn add(self, other: Point) -> Point {
|
||||
Point {
|
||||
x: self.x + other.x,
|
||||
y: self.y + other.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Millimeters(u32);
|
||||
struct Meters(u32);
|
||||
|
||||
// Using different types
|
||||
impl Add<Meters> for Millimeters {
|
||||
type Output = Millimeters;
|
||||
|
||||
fn add(self, other: Meters) -> Millimeters {
|
||||
Millimeters(self.0 + (other.0 * 1000))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -71,6 +71,8 @@ let (_x,_y,_z) = tup;
|
||||
//Accessing
|
||||
let _five_hundred = tup.0;
|
||||
let _six_point_four = tup.1;
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Arrays
|
||||
@@ -93,3 +95,16 @@ let _two = b[1];
|
||||
// Won't compile because we know the size at compile time
|
||||
// let n = a[10];
|
||||
```
|
||||
|
||||
## Type Aliases
|
||||
|
||||
```rust
|
||||
type Kilometers = i32;
|
||||
type Thunk = Box<dyn Fn() + Send + 'static>;
|
||||
|
||||
// RESULT SHORTHAND
|
||||
type Result<T> = std::result::Result<T, std::io::Error>;
|
||||
fn _result_shorthand() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
@@ -7,7 +7,9 @@
|
||||
{"text": "Enums", "link": "/rust/basics/enums"},
|
||||
{"text": "Functions", "link": "/rust/basics/funcs"},
|
||||
{"text": "Control Flow", "link": "/rust/basics/controlflow"},
|
||||
{"text": "Structs", "link": "/rust/basics/structs"}
|
||||
{"text": "Ownership", "link": "/rust/basics/ownership"},
|
||||
{"text": "Structs", "link": "/rust/basics/structs"},
|
||||
{"text": "Collections", "link": "/rust/basics/collections"}
|
||||
]
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user