notes/rust/rust.md

228 lines
9.5 KiB
Markdown

---
title: Rust
---
# Programming Rust
## Chapter 3
- Treats characters as distinct from the numeric types; a char is neither a u8 nor an i8
- Requires array indices to be usize
- In debug builds, rust checks for integer overflow in arithmetic. In release build, the addition would wrap to a negative number. When we want wrapping arithmetic, use methods
``` rust
let x = big_val.wrapping_add(1);
```
- Rust currently permits an extra trailing comma everywhere commas are used: function arguments, arrays, struct and enum definitions
### Arrays, Vectors and Slices
- Given a value v of any of `Arrays`, `Vectors` or `Slices`, `i` in `v[i]` must be a `usize` value, can't use any other integer type as index
- Rust has no notion of an uninitialized array
- The type `[T; N]` represents an array of N values, each of type T. An array's size is a constant determined at compile time, and is part of the type; you can't append new elements or shrink an array.
- The type `Vec<T>`, called a vector of `Ts`, is a dynamically allocated, growable sequence of values of type T. A vector's elements live on the heap, so you can resize vectors at will.
- The types `&[T]` and `&mut[T]`, called a shared slice of `Ts` and mutable slice of `Ts`, are references to a series of elements that are part of some other value, like an array or vector. One can think of a slice as a pointer to it's first element, together with the count of the number of elements one can access starting at that point. A mutable slice `&mut [T]` lets one read and modify elements, but can't be shared; a shared slice `&[T]` lets one share access among several readers, but doesn't let one modify elements.
- The useful methods one did likes to see on arrays - iterating over elements, searching, sorting, filling, filtering and so on - all appear as methods of slices, not arrays. But, Rust implicitly converts a reference to an array to a slice when searching for methods, so you can call any slice method on an array directly.
1. Slices
- A slice, written `[T]` without specifying the length, is a region of an array or vector. Since a slice can be any length, slices cannot be stored directly in variables or passed as function arguments.
- A reference to a slice is a *fat pointer*: a two word value comprising a pointer to the slice's first element, and the number of elements in the slice.
- Whereas an ordinary reference is a non-owning pointer to a single value, a reference to a slice is non-owning pointer to several values. This makes slices a good choice when you want to write a function that operates on any homogenous data series, whether stored in an array, vector, stack or heap.
- For example, here's a function that prints a slice of numbers, one per line:
``` rust
fn print (n: &[f64]) {
for elt in n {
println!("{}", elt);
}
}
```
- Because the above function takes a slice reference as an argument, you can apply it to either a vector or an array.
### String
- For creating new string at run time use `String`.
- The type `&mut str` does exist, but it is not useful, since almost any operation on UTF-8 can change its overall byte length, and a slice cannot reallocate its referent.
## Chapter 4 - Ownership
- As a rule of thumb, any type that needs to do something special when a value is dropped cannot be copy.
- By default, `struct` and `enum` types are not `Copy`.
### Reference as Values
- In C++, references are created implicitly by conversion, and dereferenced implicitly too.
let x = 10;
int &r = x;
assert (r == 10);
r = 20;
- In Rust, references are created explicity with the `&` operator, and deferenced explicity with the `*` operator.
``` rust
let x = 10;
let r = &x;
assert!(*r == 10);
```
- Since references are widely used in Rust, the `.` operator implicitly deferences its left operand.
- The `.` operator can also implicity borrow a reference to its left operand, if needed for a method call.
- Where as C++ converts implicitly between references and lvalues (that is, expressions referring to locations in memory), with these conversions appearing anywhere they are needed, in Rust you use the `&` and `*` operators to create and follow references, with the exception of `.` operator, which borrows and dereferences implicitly.
- In C++, assigning to a reference stores the value in its referent. There is no way to point a C++ reference to a location other than the one it was initialized with.
### References to References
- Rust permits references to references.
``` rust
struct Point { x: i32, y: i32 }
let point = Point { x: 1000, y: 729 };
let r: &Point = &point;
let rr: &&Point = &r;
let rrr: &&&Point = &rr;
```
- The `.` operator follows as many references as it takes to find its target. So an expression `rrr.y`, guided by the type of `rrr`, actually traverses three references to get to the `Point` before fetching its `y` field.
### Sharing vs Mutation
- Shared access is read only access.
- Mutable access is exclusive access.
## Chapter 6 - Expressions
### Functions and Method calls
One quirk of Rust syntax is that in a function call or method call, the usual syntax for generic types, `Vec<T>`, does not work.
``` rust
return Vec<i32>::with_capacity(1000); // Error: Something about chained comparisons
let ramp = (0 .. n).collect<Vec<i32>>(); // Same error
```
The problems is that in expressions, `<` is the less-than operator. The Rust compiler helpfully suggests writing `::<T>` in this case, and that solves the problem:
``` rust
return Vec::<i32>::with_capacity(1000);
let ramp = (0 .. n).collect::<Vec<i32>>();
```
The symbol `::<...>` is affectionately known in the Rust community as the *turbofish*.
Alternatively, it is often possible to drop the type parameters and let Rust infer them.
``` rust
return Vec::with_capacity(1000);
let ramp : Vec<i32> = (0 .. n).collect();
```
It's considered good style to omit the types whenever they can be inferred.
### Fields and elements
Rust ranges are half open: they include the start value, if any, but not the end value.
### Reference operators
The unary `*` operator is used to access the value pointed to by a reference. Rust automatically follows references when one uses the `.` operator to access a field or method, so the `*` operator is necessary only when we want to read or write the entire value that the reference points to.
For example, sometimes an iterator produces references, but the program
needs the underlying values:
``` rust
let padovan: Vec<u64> = compute_padovan_sequence(n);
for elem in &padovan {
draw_triangle(turtle, *elem);
}
```
In this example, the type of `elem` is `&u64`, so `*elem` is a `u64`.
## Chapter 7 - Error Handling
### Result Type Aliases
Sometimes you will see Rust documentation that seems to omit the error type of a `Result`:
``` rust
fn remove_fule(path: &Path) -> Result<()>
```
This means a `Result` type alias is being used.
A type alias is a kind of shorthand for type names. Modules often define a `Result` type alias to avoid having to repeat an error type that's used consistently by almost every function in the module. For example, the standard library's `std::io` module includes this line of code.
``` rust
pub type Result<T> = result::Result<T, Error>;
```
This defines a public type `std::io::Result<T>`. It's an alias for `Result<T, E>`, but hardcoding `std::io::Error` as the error type. In practical terms, this means that if you write `use std::io`, then Rust will understand `io::Result<String>` as shorthand for `Result<String, io::Error>`.
When something like `Result<>` appears in the online documentation, you can click on the identifier `Result` to see which type alias is being used and learn the error type. In practice, it's usually obvious from the context.
### Chapter 8 - Crates and Modules
### Chapter 9 - Structs
The fact that any type can have methods is one reason Rust doesn't use the term `object` much, preferring to call everything a `value`.
### Chapter 10 - Enums and Patterns
### Chapter 11 - Traits and Generics
### Chapter 13 - Utility Traits
1. Drop
- If a type implements `Drop`, it cannot implement the `Copy` trait.
2. Deref and DerefMut
- Rust doesn't try deref coercions to satisfy type variable bounds.
### Chapter 14 - Closures
- Rust can infer the argument type and return type from how the closure is used.
- Closures do not have the same type as functions.
- Every closure has its own type, because a closure may contain data: values either borrowed or stolen from enclosing scopes. This can be any number of variables, in any combination of types. So every closure has an ad hoc type created by the compiler, large enough to hold that data. No two closures have exactly the same type. But every closure implements a `Fn` trait.
- Any closure that requires `mut` access to a vvalue, but doesn't drop any values, is a `FnMut` closure.
- Closures summary
- `Fn` is the family of closures and functions that one can call multiple times without restriction. This highest category also includes all functions.
- `FnMut` is the family of closures that can be called multiple times if the closure itself is declared `mut`.
- `FnOnce` is the family of closures that can be called once, if the caller owns the closure.
# Articles on async-await
- https://os.phil-opp.com/async-await/
- https://msarmi9.github.io/posts/async-rust/
- https://fasterthanli.me/articles/pin-and-suffering
- https://smb374.github.io/an-adventure-in-rust-s-async-runtime-model.html
- https://notes.eatonphil.com/lua-in-rust.html