show line numbers in conde snippets

This commit is contained in:
Marcello 2023-10-20 18:22:46 +02:00
parent cd1df0e376
commit 255a68d673
82 changed files with 1249 additions and 1251 deletions

View file

@ -2,7 +2,7 @@
## Creating a project
```ps1
```ps1 linenums="1"
cargo new project_name # creates project folder and basic files
cargo new --vcs=git project_name # init project as git repo
```
@ -11,7 +11,7 @@ cargo new --vcs=git project_name # init project as git repo
Inside the project directory:
```ps1
```ps1 linenums="1"
cargo build # build project and download eventual needed dependencies
cargo build --release # build project for release (build + optimisations)
cargo run # executes the built executable
@ -22,7 +22,7 @@ cargo check # verifies buildability without producing an executable
In `Cargo.toml`:
```toml
```toml linenums="1"
[dependencies]
crate_name = "<version_number>"
```
@ -70,7 +70,7 @@ A path can take two forms:
Both absolute and relative paths are followed by one or more identifiers separated by double colons (`::`).
```rs
```rs linenums="1"
module::function(); // rel path (same crate)
super::function();; // rel path starting in outer module (same crate)
@ -88,7 +88,7 @@ The way privacy works in Rust is that all items (functions, methods, structs, en
Items in a parent module cant use the private items inside child modules, but items in child modules can use the items in their ancestor modules.
The reason is that child modules wrap and hide their implementation details, but the child modules can see the context in which theyre defined.
```rs
```rs linenums="1"
mod module {
fn func() {}
}
@ -109,7 +109,7 @@ In contrast, if an enum is made public, all of its variants are then public.
### `use`
```rs
```rs linenums="1"
use <crate_name>::module; // import module (abs path, other crate)
use crate::module; // import module (abs path, same crate)
use self::module; // import module (rel path, same crate)
@ -125,7 +125,7 @@ module::function(); // use func w/ shorter path
## Separating into multiple files
```txt
```txt linenums="1"
src
|_main.rs --> default executable file
|_lib.rs --> default library file
@ -134,7 +134,7 @@ src
| |_submodule.rs --> submodule
```
```rs
```rs linenums="1"
// main.rs
mod module; // declare module directory as a module

View file

@ -2,7 +2,7 @@
## Basics
```rs
```rs linenums="1"
use <module>; // bring a type into scope
fn main() { //program entry point
@ -12,7 +12,7 @@ fn main() { //program entry point
### Standard Output
```rs
```rs linenums="1"
// macro (func have no "!")
println!("Value: {}", value); // {} is a placeholder for a value or variable
println!("Values: {1}, {0}", value1, value2); // use index to print values
@ -30,7 +30,7 @@ print!();
### Standard Input
```rs
```rs linenums="1"
use io;
let mut buffer = String::new();
@ -50,7 +50,7 @@ An associated function is implemented on a type rather than on a particular inst
By default variables are *immutable*.
```rs
```rs linenums="1"
let var1 = value; // immutable var init
let var2: Type = value; // explicit type annotation
@ -68,7 +68,7 @@ By using let, it's possible to perform a few transformations on a value but have
The other difference between *mut* and *shadowing* is that because we're effectively creating a new variable when we use the let keyword again,
we can change the type of the value but reuse the same name.
```rs
```rs linenums="1"
let x: u32 = 10;
let x: i32 = 11; // shadowing
```
@ -88,7 +88,7 @@ let x: i32 = 11; // shadowing
#### Explicit Mathematical Operations (Integers)
```rs
```rs linenums="1"
i32::MAX.checked_add(value); // Option<i32> => None if overflow
i32::MAX.wrapping_add(value); // i32 => Wrap around
i32::MAX.saturating_add(value); // i32 => MIN <= x <= MAX (Clamp)
@ -164,7 +164,7 @@ Rust's `char` type is the language's most primitive alphabetic type.
Rust's `char` type is four bytes in size and represents a Unicode Scalar Value: range from `U+0000` to `U+D7FF` and `U+E000` to `U+10FFFF` inclusive.
```rs
```rs linenums="1"
let c: char = 'C'; // SINGLE QUOTES
let c: char = '\u{261D}'; // Unicode Code Point U+261D
let c: char = '\x2A'; // ASCII for *
@ -179,7 +179,7 @@ std::char::from:digit(2, 10); // Some(2)
### String Types
```rs
```rs linenums="1"
let s = String::new(); // create empty string
let s = String::from("string literal"); // construct string from literal
@ -205,7 +205,7 @@ s.push_str(""); // appending string literals
A tuple is a general way of grouping together a number of values with a variety of types into one compound type.
Tuples have a *fixed length*: once declared, they cannot grow or shrink in size.
```rs
```rs linenums="1"
let tup: (i32, f64, u8) = (500, 6.4, 1);
let tup = (500, 6.4, 1);
@ -219,7 +219,7 @@ tup.0 = value; // member access & update (mut be mutable)
Every element of an array must have the *same type*. Arrays in Rust have a fixed length, like tuples.
An array isn't as flexible as the `vector` type, though. A vector is a similar collection type provided by the standard library that *is allowed to grow or shrink in size*.
```rs
```rs linenums="1"
let array = [0, 1, 2, 3, 4];
let array: [Type; length] = [...];
let array: [value; length]; // repeat expression (same as python's [value] * length)
@ -249,7 +249,7 @@ The reverse is not possible since the slice lacks some information about the Str
> **Note**: When working with functions is easier to always expect a `&str` instead of a `&String`.
```rs
```rs linenums="1"
let s = String::from("string literal");
let slice: &str = &s[start..end];
@ -262,7 +262,7 @@ sequence[..end] // slice from start to end (excluded)
### Type Aliases
```rs
```rs linenums="1"
type Alias = T;
```
@ -275,7 +275,7 @@ The curly brackets tell the compiler where the function body begins and ends.
Rust doesn't care where the functions are defined, only that they're defined somewhere.
```rs
```rs linenums="1"
fn func(param: Type) { // parameters MUST have the Type annotation
// code here
}
@ -301,7 +301,7 @@ fn func() {
### if - else if - else
```rs
```rs linenums="1"
if condition {
// [...]
} else if condition {
@ -313,13 +313,13 @@ if condition {
### let if
```rs
```rs linenums="1"
let var = if condition { value } else { value }; // returned types must be the same
```
### [if-let](https://doc.rust-lang.org/rust-by-example/flow_control/if_let.html)
```rs
```rs linenums="1"
if let <pattern> = <expr> {
<block1>
} else {
@ -335,20 +335,20 @@ match <expr> {
### loop
```rs
```rs linenums="1"
// loop's forever if not explicitly stopped (return or break)
loop { }
```
### while
```rs
```rs linenums="1"
while condition { }
```
### for
```rs
```rs linenums="1"
for item in sequence.iter() { }
for item in sequence.iter_mut() { } // iterate over mutable items
@ -362,7 +362,7 @@ for i in (start..end) { }
### Range
```rs
```rs linenums="1"
.. // RangeFull
a .. // RangeFrom { start: a }
.. b // RangeTo { end: b }
@ -373,7 +373,7 @@ a ..= b // RangeInclusive::new(a, b)
### `break` & `continue`
```rs
```rs linenums="1"
while <condition> {
// [...]
@ -449,7 +449,7 @@ Cloning is an explicit action, `x.clone()`. The implementation of Clone can prov
Rust won't allow to annotate a type with the `Copy` trait if the type, or any of its parts, has implemented the `Drop` trait.
```rs
```rs linenums="1"
let s = String::new()
let t = s; // MOVE, s is now uninitialized
let u = t.clone(); // deep copy, t is still valid
@ -462,7 +462,7 @@ let x = n; // x holds a COPY of the VALUE of n
The semantics for passing a value to a function are similar to those for assigning a value to a variable. Passing a variable to a function will move or copy, just as assignment does.
```rs
```rs linenums="1"
fn main() {
let s = String::from("hello"); // s comes into scope
@ -487,7 +487,7 @@ fn makes_copy(some_integer: i32) { // some_integer comes into scope
Returning values can also transfer ownership.
```rs
```rs linenums="1"
fn main() {
let s1 = gives_ownership(); // gives_ownership moves its return value into s1
@ -537,7 +537,7 @@ A data race is similar to a race condition and happens when these three behavior
- At least one of the pointers is being used to write to the data.
- There's no mechanism being used to synchronize access to the data.
```rs
```rs linenums="1"
fn borrow(var: &Type) { // &Type indicates that the var is a reference
// here var cannot be modified
} // when var goes out of scope it doesn't get dropped because the scope didn't own it
@ -554,7 +554,7 @@ fn borrow2(var: &mut Type) {
The `.` operator can **implicitly borrow or dereference** a reference
```rs
```rs linenums="1"
let struct = Struct { field: /* [...] */ }
let ref = &struct;
@ -578,7 +578,7 @@ To define a struct enter the keyword struct and name the entire struct.
A struct's name should describe the significance of the pieces of data being grouped together.
Then, inside curly brackets, define the names and types of the pieces of data, which we call *fields*.
```rs
```rs linenums="1"
struct Struct {
field: Type,
...
@ -591,7 +591,7 @@ struct UnitStruct; // no field, useful in generics
To use a struct after defining it, create an instance of that struct by specifying concrete values for each of the fields.
```rs
```rs linenums="1"
let mut var = Struct {
field: value,
...
@ -600,7 +600,7 @@ let mut var = Struct {
### Field Init Shorthand
```rs
```rs linenums="1"
let mut var = Struct {
field, // shortened form since func param is named as the struct's field
...
@ -613,7 +613,7 @@ var.field = value; // member access
### Struct Update Syntax
```rs
```rs linenums="1"
let struct1 = Struct {
field1: value,
field2: value,
@ -631,7 +631,7 @@ let struct2 = Struct {
Use Tuple Structs to create different types easily.
To define a **tuple struct**, start with the `struct` keyword and the struct name followed by the types in the tuple.
```rs
```rs linenums="1"
struct Point(i32, i32, i32);
struct Color(i32, i32, i32);
@ -640,7 +640,7 @@ let origin = Point(0, 0, 0);
### Struct Printing
```rs
```rs linenums="1"
#[derive(Debug)] // inherit the debug trait
struct Struct { }
@ -650,7 +650,7 @@ println!("{:?}", s) // debug output: { field: value, ... }
### Associated Functions & Type-Associated Functions (aka Methods)
```rs
```rs linenums="1"
struct Struct { };
impl Struct
{
@ -667,7 +667,7 @@ Struct::type_associated_function(arg);
### Associated Consts
```rs
```rs linenums="1"
struct Struct {
const ASSOCIATED_CONST: Type = <value>;
}
@ -680,7 +680,7 @@ Struct::ASSOCIATED_CONST;
A Trait is a collection of methods representing a set of behaviours necessary to accomplish some task.
Traits can be used as generic types constraints and can be implemented by data types.
```rs
```rs linenums="1"
trait Trait {
fn method_signature(&self, param: Type) -> Type;
fn method_signature(&self, param: Type) -> Type {
@ -701,7 +701,7 @@ impl Trait for Struct {
### Fully Qualified Method Calls
```rs
```rs linenums="1"
value.method();
Type::method(value);
Trait::method(value);
@ -726,7 +726,7 @@ Derivable Traits:
- `Default`
- `Debug`
```rs
```rs linenums="1"
#[derive(Trait)] // derive a trait for the struct
#[derive(Trait, Trait, ...)] // derive multiple traits
struct Struct {
@ -738,7 +738,7 @@ struct Struct {
Trait Bound are used to require a generic to implement specific traits and guarantee that a type will have the necessary behaviours.
```rs
```rs linenums="1"
fn generic_method<T: RequiredTrait>() {}
fn generic_method<T: RequiredTrait + RequiredTrait>() {} // multiple bounds
// or
@ -760,7 +760,7 @@ fn method_signature(param: &(impl TraitOne + TraitTwo)) -> Type {}
### Trait Extensions
```rs
```rs linenums="1"
extern crate foo;
use foo::Foo;
@ -784,7 +784,7 @@ Rust accomplishes this by performing *monomorphization* of the code that is usin
Monomorphization is the process of turning generic code into specific code by filling in the concrete types that are used when compiled.
For this reason if a function as a trait as return type (trait bound), only a single type that implements the trait can be returned.
```rs
```rs linenums="1"
struct GenericStruct<T, U> {
T generic_field,
U generic_field,
@ -817,10 +817,10 @@ fn generic<T: Trait>() -> Type { } // use generic constraint
### Associated Types
_Associated types_ connect a type placeholder with a trait such that the trait method definitions can use these placeholder types in their signatures.
*Associated types* connect a type placeholder with a trait such that the trait method definitions can use these placeholder types in their signatures.
The implementor of a trait will specify the concrete type to be used instead of the placeholder type for the particular implementation.
```rs
```rs linenums="1"
trait Iterator {
type Item;
@ -848,7 +848,7 @@ In memory, a trait object is a fat pointer consisting of a pointer to the value,
> **Note**: Rust automatically converts ordinary references into trait objects when needed
```rs
```rs linenums="1"
let trait_object = &mut dyn Trait = &mut source;
let trait_object: Box<dyn Trait> = Box::new(source); // same for Rc<T>, Arc<T>, ...
```
@ -856,7 +856,7 @@ let trait_object: Box<dyn Trait> = Box::new(source); // same for Rc<T>, Arc<T>,
This works differently from defining a struct or function that uses a generic type parameter with trait bounds.
A generic type parameter can only be substituted with *one concrete type* at a time, whereas trait objects allow for *multiple* concrete types to fill in for the trait object at runtime.
```rs
```rs linenums="1"
fn func() -> Box<dyn Trait> { } // return something that implements the specified trait
```
@ -881,7 +881,7 @@ The annotation does not affect how long the references live.
In case of different lifetimes the complier will use the most restrictive.
```rs
```rs linenums="1"
// lifetime annotation syntax
fn func<'a>(x: &'a Type, y: &'a Type) -> &'a Type { }
@ -914,7 +914,7 @@ They describe situations that do not require explicit lifetime annotations.
## Enums
```rs
```rs linenums="1"
// enum definition
enum Enum
{
@ -948,7 +948,7 @@ A *match expression* is made up of *arms*. An arm consists of a *pattern* and th
> **Note**: `match` arms must be exhaustive for compilation.
```rs
```rs linenums="1"
enum Enum {
Variant1,
Variant2,
@ -988,7 +988,7 @@ Guard Expression | `<pattern> if <condition>` | `match` only
> **Note**: `..` in slices matches *any number* of elements. `..` in structs *ignores* all remaining fields
```rs
```rs linenums="1"
// unpack a struct into local variables
let Struct { local_1, local_2, local_3, .. } = source;
@ -1010,7 +1010,7 @@ A **refutable pattern** is one that might not match, like `Ok(x)`. Refutable pat
Refutable patterns are also allowed in `if let` and `while let` expressions:
```rs
```rs linenums="1"
// handle just one enum variant specially
if let Enum::VariantX(_, _) = source { }
@ -1032,7 +1032,7 @@ The `Option` type is used in many places because it encodes the very common scen
`Result<T, E>` is the type used for returning and propagating errors. It is an enum with the variants, `Ok(T)`, representing success and containing a value, and `Err(E)`, representing error and containing an error value.
```rs
```rs linenums="1"
// std implementation
enum Option<T> {
Some(T),
@ -1099,7 +1099,7 @@ Ending an expression with `?` will result in the unwrapped success (`Ok`) value,
When working with multiple error types is useful to return a "generic error" type. All the standard library error types can be represented by `Box<dyn std::Error + Send + Sync + 'static>`.
```rs
```rs linenums="1"
// convenience type aliases for the generic error
type GenericError = Box<dyn std::Error + Send + Sync + 'static>;
type GenericResult<T> = Result<T; GenericError>;
@ -1109,7 +1109,7 @@ type GenericResult<T> = Result<T; GenericError>;
### Custom Error Types
```rs
```rs linenums="1"
use thiserror::Error; // utility crate for custom errors
#[derive(Error, Debug)]
@ -1130,7 +1130,7 @@ pub struct JsonError {
Vectors allow to store more than one value in a single data structure that puts all the values next to each other in memory. Vectors can only store values of the *same type*.
Like any other struct, a vector is freed when it goes out of scope. When the vector gets dropped, all of its contents are also dropped.
```rs
```rs linenums="1"
let v: Vec<Type> = Vec<Type>::new(); // empty vec init
let mut v: vec![item1, item2, ...]; // vec init (type inferred)
@ -1149,7 +1149,7 @@ for i in mut &v {
A vector can hold different types if those type are variants of the same enum. It's also possible to use trait objects.
```rs
```rs linenums="1"
enum Enum {
Int(i32),
Float(f64),
@ -1167,7 +1167,7 @@ let v = vec![
Stores data in key-value pairs.
```rs
```rs linenums="1"
use std::collections::HashMap;
let map: HashMap<K, V> = HashMap::new();
@ -1189,7 +1189,7 @@ Within these limited contexts, the compiler is reliably able to infer the types
The first time a closure is called with an argument, the compiler infers the type of the parameter and the return type of the closure.
Those types are then locked into the closure and a type error is returned if a different type is used with the same closure.
```rs
```rs linenums="1"
// closure definition
let closure = |param1, param2| <expr>;
let closure = |param1, param2| {/* multiple lines of code */};
@ -1207,7 +1207,7 @@ To define structs, enums, or function parameters that use closures generics and
The `Fn` traits are provided by the standard library. All closures implement at least one of the traits: `Fn`, `FnMut`, or `FnOnce`.
```rs
```rs linenums="1"
struct ClosureStruct<T> where T: Fn(u32) -> u32 {
closure: T,
}
@ -1236,7 +1236,7 @@ All closures implement `FnOnce` because they can all be called at least once. Cl
To force the closure to take ownership of the values it uses in the environment, use the `move` keyword before the parameter list.
This technique is mostly useful when passing a closure to a new thread to move the data so it's owned by the new thread.
```rs
```rs linenums="1"
let closure = move |param| <expr>;
```
@ -1246,7 +1246,7 @@ The *iterator pattern* allows to perform some task on a sequence of items in tur
In Rust, iterators are *lazy*, meaning they have no effect until a call to methods that consume the iterator to use it up.
```rs
```rs linenums="1"
// iterator trait
pub trait Iterator {
type Item;
@ -1266,7 +1266,7 @@ Other methods defined on the `Iterator` trait, known as *iterator adaptors*, all
It's possible to chain multiple calls to iterator adaptors to perform complex actions in a readable way.
But because all iterators are lazy, a call one of the consuming adaptor methods is needed to get the results.
```rs
```rs linenums="1"
let iterator: = vec![1, 2, 3];
iterator
.map(|x| x + 1) // iterator adapter
@ -1276,7 +1276,7 @@ iterator
### Custom Iterators
```rs
```rs linenums="1"
struct Counter {
count: u32,
}
@ -1334,7 +1334,7 @@ Boxes don't have performance overhead, other than storing their data on the heap
- Transferring ownership of a large amount of data but ensuring the data won't be copied when you do so
- Owning a value and which implements a particular trait rather than being of a specific type
```rs
```rs linenums="1"
let _box = Box::new(pointed_value);
```
@ -1343,7 +1343,7 @@ let _box = Box::new(pointed_value);
Implementing the `Deref` trait allows to customize the behavior of the dereference operator, `*`.
By implementing `Deref` in such a way that a smart pointer can be treated like a regular reference.
```rs
```rs linenums="1"
struct CustomSmartPointer<T>(T);
impl<T> CustomSmartPointer<T> {
@ -1370,7 +1370,7 @@ let v = *(s.deref());
It works only on types that implement the `Deref` trait and converts such a type into a reference to another type.
Deref coercion was added to Rust so that programmers writing function and method calls don't need to add as many explicit references and dereferences with `&` and `*`.
```rs
```rs linenums="1"
fn hello(name: &str) {
println!("Hello {}", name);
}
@ -1395,7 +1395,7 @@ Rust does *deref coercion* when it finds types and trait implementations in thre
`Drop` allows to customize what happens when a value is about to go out of scope. It-s possible to provide an implementation for the `Drop` trait on any type.
```rs
```rs linenums="1"
struct CustomSmartPointer<T>(T);
impl<T> Drop for CustomSmartPointer<T> {
@ -1419,7 +1419,7 @@ Rust provides the *reference-counted* pointer types `Rc<T>` and `Arc<T>`.
The `Rc<T>` and `Arc<T>` types are very similar; the only difference between them is that an `Arc<T>` is safe to share between
threads directly (the name Arc is short for *atomic* reference count) whereas a plain `Rc<T>` uses faster non-thread-safe code to update its reference count.
```rs
```rs linenums="1"
use std::rc::Rc;
let s: Rc<String> = Rc::new("some string".to_string());
@ -1477,10 +1477,10 @@ This creates memory leaks because the reference count of each item in the cycle
### Creating Threads
The `thread::spawn` function creates a new tread that will execute the passed closure. Any data captured by the closure is _moved_ to the capturing thread.
The `thread::spawn` function creates a new tread that will execute the passed closure. Any data captured by the closure is *moved* to the capturing thread.
The thread's **handle** can be used to wait for completion and retieve the computation result or errors if any.
```rs
```rs linenums="1"
// if no data is captured the move keyword can be removed
let handle = std::thread::spawn(move || { /* ... */ });
@ -1490,12 +1490,12 @@ handle.join().unwrap();
> **Note**: a thread's execution can be termiated early if it's not joined and the main process terminates before the thread had completed it's work.
> **Note**: if a thread panics the handle will return the panic message so that it can be handled.
### Channnels
### Channels
To accomplish message-sending concurrently Rust's standard library provides an implementation of _channels_.
To accomplish message-sending concurrently Rust's standard library provides an implementation of *channels*.
A **channel** is a general programming concept by which data is sent from one thread to another.
```rs
```rs linenums="1"
let (sender, receiver) = std::sync::mpsc::channel(); // the sender can be cloned to create multiple transmitters
let sender_1 = sender.clone();
@ -1521,13 +1521,13 @@ for message in receiver { } // receive multiple values (iteration stops when cha
The `Send` marker trait indicates that ownership of values of the type implementing `Send` can be transferred between threads. Any type composed entirely of `Send` types is automatically marked as `Send`. Almost all primitive types are `Send`, aside from raw pointers.
The `Sync` marker trait indicates that it is safe for the type implementing `Sync` to be referenced from multiple threads. In other words, any type `T` is `Sync` if `&T` (an immutable reference to `T`) is `Send`, meaning the reference can be sent safely to another thread. Similar to `Send`, primitive types are `Sync`, and types composed entirely of types that are `Sync `are also `Sync`.
The `Sync` marker trait indicates that it is safe for the type implementing `Sync` to be referenced from multiple threads. In other words, any type `T` is `Sync` if `&T` (an immutable reference to `T`) is `Send`, meaning the reference can be sent safely to another thread. Similar to `Send`, primitive types are `Sync`, and types composed entirely of types that are `Sync`are also `Sync`.
## Files
### Reading Files
```rs
```rs linenums="1"
use std::fs;
let contents: Vec<u8> = fs::read("path/to/file").unwrap_or_default();
@ -1538,7 +1538,7 @@ contents.lines(); // iterator over text lines
### Writing Files
```rs
```rs linenums="1"
use std::fs;
use std::io::Write; // write trait
// or
@ -1562,7 +1562,7 @@ The extern keyword is used in two places in Rust:
`extern` is used in two different contexts within FFI. The first is in the form of external blocks, for declaring function interfaces that Rust code can call foreign code by.
```rs
```rs linenums="1"
#[link(name = "my_c_library")]
extern "C" {
fn my_c_function(x: i32) -> bool;
@ -1575,7 +1575,7 @@ Working with non-Rust languages and FFI is inherently unsafe, so wrappers are us
The mirror use case of FFI is also done via the extern keyword:
```rs
```rs linenums="1"
#[no_mangle]
pub extern "C" fn callable_from_c(x: i32) -> bool {
x % 3 == 0

View file

@ -2,7 +2,7 @@
## Test Functions
```rs
```rs linenums="1"
// module code here
#[cfg(test)]
@ -33,7 +33,7 @@ mod tests {
## Controlling How Tests Are Run
```sh
```sh linenums="1"
cargo test -- --test-threads=<number> # run tests in parallel (1 no parallelism)
cargo test -- --show-output # show content printed in stdout in each test
cargo test <test_name> # run only a specific test