mirror of
https://gitlab.com/djdietrick/docs
synced 2026-05-03 03:50:54 -04:00
Finished rust basics, moving on to advanced
This commit is contained in:
107
docs/rust/advanced/generics.md
Normal file
107
docs/rust/advanced/generics.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Generics
|
||||
|
||||
We use generics for functions and structs when we want them to support multiple types instead of defining multiple functions or structs.
|
||||
|
||||
## Functions
|
||||
|
||||
```rust
|
||||
fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T { // As long as T has the trait PartialOrd
|
||||
let mut largest = &list[0];
|
||||
|
||||
for item in list {
|
||||
if item > largest {
|
||||
largest = item;
|
||||
}
|
||||
}
|
||||
|
||||
largest
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let number_list = vec![34, 50, 25, 100, 65];
|
||||
|
||||
let result = largest(&number_list);
|
||||
println!("The largest number is {}", result);
|
||||
|
||||
let char_list = vec!['y', 'm', 'a', 'q'];
|
||||
|
||||
let result = largest(&char_list);
|
||||
println!("The largest char is {}", result);
|
||||
}
|
||||
```
|
||||
|
||||
## Structs
|
||||
|
||||
```rust
|
||||
struct Point<T> {
|
||||
x: T,
|
||||
y: T,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let integer = Point { x: 5, y: 10 };
|
||||
let float = Point { x: 1.0, y: 4.0 };
|
||||
}
|
||||
|
||||
struct Point<T, U> {
|
||||
x: T,
|
||||
y: U,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let both_integer = Point { x: 5, y: 10 };
|
||||
let both_float = Point { x: 1.0, y: 4.0 };
|
||||
let integer_and_float = Point { x: 5, y: 4.0 };
|
||||
}
|
||||
```
|
||||
|
||||
## Enums
|
||||
|
||||
```rust
|
||||
enum Option<T> {
|
||||
Some(T),
|
||||
None,
|
||||
}
|
||||
enum Result<T, E> {
|
||||
Ok(T),
|
||||
Err(E),
|
||||
}
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
You can define methods that only apply when the generic is of a certain type.
|
||||
|
||||
```rust
|
||||
struct Point<T> {
|
||||
x: T,
|
||||
y: T,
|
||||
}
|
||||
|
||||
impl<T> Point<T> {
|
||||
fn x(&self) -> &T {
|
||||
&self.x
|
||||
}
|
||||
}
|
||||
|
||||
impl Point<f32> { // Only availble to Point<f32>s
|
||||
fn distance_from_origin(&self) -> f32 {
|
||||
(self.x.powi(2) + self.y.powi(2)).sqrt()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let p = Point { x: 5, y: 10 };
|
||||
|
||||
println!("p.x = {}", p.x());
|
||||
}
|
||||
|
||||
impl<X1, Y1> Point<X1, Y1> {
|
||||
fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> {
|
||||
Point {
|
||||
x: self.x,
|
||||
y: other.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
105
docs/rust/advanced/traits.md
Normal file
105
docs/rust/advanced/traits.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Traits
|
||||
|
||||
A trait is essentially an interface. It defines functionality that can then be shared with other types.
|
||||
|
||||
## Defining a trait
|
||||
|
||||
You do not have to provide a function body when defining a trait. If not, the implementation must be done on each type that inherits this trait. It is possible to define a default behavior and then later override it. Trait methods can also call other trait methods, even if they do not have default implementations. Those functions called without default implentations will then need to be defined in the inheriting type.
|
||||
|
||||
```rust
|
||||
pub trait Summary {
|
||||
fn summarize(&self) -> String;
|
||||
}
|
||||
|
||||
pub trait Summary {
|
||||
fn summarize(&self) -> String {
|
||||
String::from("(Read more...)")
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Summary {
|
||||
fn summarize_author(&self) -> String;
|
||||
|
||||
fn summarize(&self) -> String {
|
||||
format!("(Read more from {}...)", self.summarize_author())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Implementing a trait
|
||||
|
||||
To implement a trait, you use the `impl <trait> for <struct>` syntax.
|
||||
|
||||
```rust
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Tweet {
|
||||
pub username: String,
|
||||
pub content: String,
|
||||
pub reply: bool,
|
||||
pub retweet: bool,
|
||||
}
|
||||
|
||||
impl Summary for Tweet {
|
||||
fn summarize(&self) -> String {
|
||||
format!("{}: {}", self.username, self.content)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Traits as parameters
|
||||
|
||||
You can also use traits as you would types if you only want to call functions defined in the trait.
|
||||
|
||||
```rust
|
||||
pub fn notify(item: &impl Summary) {
|
||||
println!("Breaking news! {}", item.summarize());
|
||||
}
|
||||
// Or
|
||||
pub fn notify<T: Summary>(item: &T) {
|
||||
println!("Breaking news! {}", item.summarize());
|
||||
}
|
||||
|
||||
// With multiple traits
|
||||
pub fn notify(item: &(impl Summary + Display)) {
|
||||
// do something
|
||||
}
|
||||
// Or
|
||||
pub fn notify<T: Summary + Display>(item: &T) {
|
||||
// do something
|
||||
}
|
||||
|
||||
// With where clause
|
||||
fn some_function<T, U>(t: &T, u: &U) -> i32
|
||||
where T: Display + Clone,
|
||||
U: Clone + Debug
|
||||
{
|
||||
// do something
|
||||
}
|
||||
```
|
||||
|
||||
## Returning types that implement traits
|
||||
|
||||
```rust
|
||||
fn returns_summarizable() -> impl Summary {
|
||||
Tweet {
|
||||
username: String::from("horse_ebooks"),
|
||||
content: String::from(
|
||||
"of course, as you probably already know, people",
|
||||
),
|
||||
reply: false,
|
||||
retweet: false,
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -17,4 +17,27 @@ enum Message {
|
||||
ChangeColor(i32, i32, i32),
|
||||
}
|
||||
let _mess = Message::Write(String::from("message"));
|
||||
```
|
||||
|
||||
## Built in Enums
|
||||
|
||||
### Result
|
||||
|
||||
```rust
|
||||
enum Result<T,E> {
|
||||
Ok(T),
|
||||
Err(E)
|
||||
}
|
||||
```
|
||||
|
||||
### Option
|
||||
|
||||
```rust
|
||||
enum Option<T> {
|
||||
Some(T),
|
||||
None
|
||||
}
|
||||
|
||||
let _absent_option: Option<i32> = None;
|
||||
let _present_option: Option<i32> = Some(5);
|
||||
```
|
||||
67
docs/rust/basics/errors.md
Normal file
67
docs/rust/basics/errors.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Errors
|
||||
|
||||
## Unrecovable errors
|
||||
|
||||
These errors are called panics in rust, and they cause the program to quit. They can be caused by something like an out of index error, or explicitly with the `panic!` macro.
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
panic!("crash and burn");
|
||||
}
|
||||
```
|
||||
|
||||
## Results: Recoverable Errors
|
||||
|
||||
A `Result` is an enum that allows for handling when a function does not occur as expected.
|
||||
|
||||
```rust
|
||||
enum Result<T, E> {
|
||||
Ok(T),
|
||||
Err(E),
|
||||
}
|
||||
|
||||
let greeting_file_result = File::open("hello.txt"); // Returns a result
|
||||
|
||||
let greeting_file = match greeting_file_result {
|
||||
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);
|
||||
}
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Shortcuts for panicing on error
|
||||
|
||||
`unwrap` can be used to throw an error automatically, or panic with an error message with `expect`.
|
||||
|
||||
```rust
|
||||
let greeting_file = File::open("hello.txt").unwrap();
|
||||
let greeting_file = File::open("hello.txt")e.expect("hello.txt should be included in this project");
|
||||
```
|
||||
|
||||
### Propagate an error
|
||||
|
||||
If an error is returned to a function that also returns a `Result` with a comparable error type, then you can use the `?` to return an error and propagate the error to the caller.
|
||||
|
||||
```rust
|
||||
fn read_username_from_file() -> Result<String, io::Error> {
|
||||
let mut username_file = File::open("hello.txt")?;
|
||||
let mut username = String::new();
|
||||
username_file.read_to_string(&mut username)?;
|
||||
Ok(username)
|
||||
}
|
||||
```
|
||||
|
||||
The same syntax can be used with Options as well.
|
||||
|
||||
```rust
|
||||
fn last_char_of_first_line(text: &str) -> Option<char> {
|
||||
text.lines().next()?.chars().last()
|
||||
}
|
||||
```
|
||||
@@ -121,6 +121,12 @@ fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
|
||||
}
|
||||
```
|
||||
|
||||
## Automatic lifetimes (Elision)
|
||||
|
||||
1. Compiler automatically assigns one lifetime to each input parameter that is a reference.
|
||||
2. If there is exactly one lifetime parameter, that lifetime is assigned to all outputs references.
|
||||
3. If there are multiple input lifetime parameters and one of them is `&self` or `&mut self`, the lifetime of `self` is applyed to all output references.
|
||||
|
||||
### 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.
|
||||
@@ -159,4 +165,29 @@ fn main() {
|
||||
part: first_sentence,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### In implementatons
|
||||
|
||||
For method implementations, you use the same syntax as generics. For method functions that take `&self` as an argument, the third elision rule applies and the return value has the lifetime of the instance.
|
||||
|
||||
```rust
|
||||
impl<'a> ImportantExcerpt<'a> {
|
||||
fn level(&self) -> i32 {
|
||||
3
|
||||
}
|
||||
|
||||
fn announce_and_return_part(&self, announcement: &str) -> &str {
|
||||
println!("Attention please: {}", announcement);
|
||||
self.part
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Static Lifetimes
|
||||
|
||||
This is a special lifetime that denotes that the reference can life for the entire duration of the program. String literals are an example since they are built into the binary so they are always available.
|
||||
|
||||
```rust
|
||||
let s: &'static str = "I have a static lifetime.";
|
||||
```
|
||||
77
docs/rust/basics/packaging.md
Normal file
77
docs/rust/basics/packaging.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Packaging, Crates, and Modules
|
||||
|
||||
## Crates
|
||||
|
||||
A crate is the smallest piece of code. It can be either a library or a binary.
|
||||
|
||||
Binaries are programs that compile into an executable, and must have a `main` function. The entrypoint to a binary is `src/main.rs` within the package.
|
||||
|
||||
Libraries are meant to be imported and used by other projects. The entrypoint to a library is `src/lib.rs` and the compiler will create a library with the same name as the package.
|
||||
|
||||
## Packages
|
||||
|
||||
A package is one or more crates that do something. It contains a `cargo.yaml` file that describes how to build the package. A package can have multiple binary crates, but only one library crate.
|
||||
|
||||
## Modules
|
||||
|
||||
Modules provide a way to break code into different files and use their paths to import/export functionality.
|
||||
|
||||
### Cheat Sheet from the Rust Book
|
||||
|
||||
- Start from the crate root: When compiling a crate, the compiler first looks in the crate root file (usually `src/lib.rs` for a library crate or `src/main.rs` for a binary crate) for code to compile.
|
||||
- Declaring modules: In the crate root file, you can declare new modules; say, you declare a “garden” module with `mod garden;`. The compiler will look for the module’s code in these places:
|
||||
- Inline, within curly brackets that replace the semicolon following `mod garden`
|
||||
- In the file `src/garden.rs`
|
||||
- In the file `src/garden/mod.rs`
|
||||
- Declaring submodules: In any file other than the crate root, you can declare submodules. For example, you might declare `mod vegetables;` in `src/garden.rs`. The compiler will look for the submodule’s code within the directory named for the parent module in these places:
|
||||
- Inline, directly following `mod vegetables`, within curly brackets instead of the semicolon
|
||||
- In the file `src/garden/vegetables.rs`
|
||||
- In the file `src/garden/vegetables/mod.rs`
|
||||
- Paths to code in modules: Once a module is part of your crate, you can refer to code in that module from anywhere else in that same crate, as long as the privacy rules allow, using the path to the code. For example, an `Asparagus` type in the garden vegetables module would be found at `crate::garden::vegetables::Asparagus`.
|
||||
- Private vs public: Code within a module is private from its parent modules by default. To make a module public, declare it with `pub mod` instead of `mod`. To make items within a public module public as well, use `pub` before their declarations.
|
||||
- The `use` keyword: Within a scope, the `use` keyword creates shortcuts to items to reduce repetition of long paths. In any scope that can refer to `crate::garden::vegetables::Asparagus`, you can create a shortcut with `use crate::garden::vegetables::Asparagus;` and from then on you only need to write `Asparagus` to make use of that type in the scope.
|
||||
|
||||
## Making things public
|
||||
|
||||
By default, everything you declare in rust is private. To make them public and usable by other files, use the `pub` keyword.
|
||||
|
||||
```rust
|
||||
pub struct Breakfast {
|
||||
pub toast: String,
|
||||
seasonal_fruit: String,
|
||||
}
|
||||
|
||||
impl Breakfast {
|
||||
pub fn summer(toast: &str) -> Breakfast {
|
||||
Breakfast {
|
||||
toast: String::from(toast),
|
||||
seasonal_fruit: String::from("peaches"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Appetizer {
|
||||
Soup,
|
||||
Salad,
|
||||
}
|
||||
```
|
||||
|
||||
For libraries, or any other module, you can re-export a module with `pub use`. Then you can import it from that re-exporting module's scope if it is deeply nested.
|
||||
|
||||
## Additional shorthands
|
||||
|
||||
```rust
|
||||
use std::cmp::Ordering;
|
||||
use std::io;
|
||||
// Becomes...
|
||||
use std::{cmp::Ordering, io};
|
||||
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
// Becomes...
|
||||
use std::io::{self, Write};
|
||||
|
||||
// Brings everything from that module into scope
|
||||
use std::collections::*;
|
||||
// Use with caution, because it becomes difficult to determine what is in scope
|
||||
```
|
||||
@@ -16,20 +16,6 @@ let _b: bool = true;
|
||||
|
||||
// Char, specified by single-quotes
|
||||
let _c: char = 'c';
|
||||
|
||||
// Options
|
||||
// enum Option<T> {
|
||||
// Some(T),
|
||||
// None
|
||||
// }
|
||||
let _absent_option: Option<i32> = None;
|
||||
let _present_option: Option<i32> = Some(5);
|
||||
|
||||
// Results
|
||||
// enum Result<T,E> {
|
||||
// Ok(T),
|
||||
// Err(E)
|
||||
// }
|
||||
```
|
||||
|
||||
## Slices
|
||||
|
||||
@@ -9,7 +9,16 @@
|
||||
{"text": "Control Flow", "link": "/rust/basics/controlflow"},
|
||||
{"text": "Ownership", "link": "/rust/basics/ownership"},
|
||||
{"text": "Structs", "link": "/rust/basics/structs"},
|
||||
{"text": "Collections", "link": "/rust/basics/collections"}
|
||||
{"text": "Collections", "link": "/rust/basics/collections"},
|
||||
{"text": "Packaging", "link": "/rust/basics/packaging"},
|
||||
{"text": "Errors", "link": "/rust/basics/errors"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"text": "Advanced",
|
||||
"items": [
|
||||
{"text": "Generics", "link": "/rust/advanced/generics"},
|
||||
{"text": "Traits", "link": "/rust/advanced/traits"}
|
||||
]
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user