Rust - hpepper/henpep-dev-tools GitHub Wiki
-
How to do unit testsRust allows us to use multiple let statements to create new variables with the same names as old variables, but we can't just assign a new value to an existing variable, unless that variable is mutable.
-
How to do Documentation
-
CLI
-
Multi-threading
-
TCP/UDP
-
WebSocket
-
GUI
-
Audio
-
Closure, and 'move'
- Arb18: Rust quick start guide
- Bal17: Rust Essentials {Ivo Balbaert}
- Hof19: Programming WebAssembly with Rust; Kevin Hoffman
- Kol19: Hands-On Microservices with Rust; Denis Kolodin
- Kai17: Mastering Rust {Vesa Kaihlavirta}
- Wol21: Wolverson Hands-on Rust
- Bos23: Rust atomics and locks {Mara Bos}
- get started with rust
- Rust code formatting RFCs
- The rust programming language
- The Rust reference
- Rust Cookbook
- Zero To Production In Rust
- Documentation of crates
-
24 Days of rust
- There is night mode under the 'A' at the top line.
- Rust training
- The Rust Performance Book
- Rust and WebAssembly
- Edge - Rust and WebAssembly
- Trigonometry
- Crate list
- Game dev resources
- Rust OS comparison
- A curated list of Rust code and resources
- A bunch of links to blog posts, articles, videos, etc for learning Rust.
others:
- benchmarking
- criterion
- how-to-do-code-coverage-in-rust
- yewstack
- reading_and_writing_packets
- morebasicgames
- morebasicgames 21
- basicgames
- BASIC_Computer_Games
- introduction
- custom_window_frame
- h11-03-test-organization
- rust lang book
- opentelemetry_jaeger
- tracing_jaeger
- reactivemanifesto
- Choosing a Rust web framework
TODO read:
YouTube
-
Option and Result
- conversion
.ok?.map
- conversion
- A Simpler Way to See Results
- adapter - produces a new iterator from the old iterator(Bal17,p98)
-
filter()(Bal17,p98) -
map()transforms the item in the range(Bal17,p98) - For the rest see the docs of module std::iter
-
- borrowing - see: reference
- TODO understand this: Rust’s safety features make a mutable borrow exclusive. If a variable is mutably borrowed, it cannot be borrowed—mutably or immutably—by other statements while the borrow remains.(Wol21,p112)
- Closure - seems to be a lambda function? Closures
- aka anonymous function(Bal17, p94).
-
let triples = |n| { 3 * n };-
||- mark the start of a closure, and contains the parameters passed to the closure(Bal17,p94).
-
- There is no need to indicate the type of the parameters or that of the return value: a closure can infer these types from the context in which it is called(Bal17,p94).
- knows the value of x and all other variables that are available in its surrounding scope; it closes them in(Bal17,p94).
- For now, think of a closure as a function you define in place. The inline closure
|visitor| visitor.name == nameis the same as defining a function: FirstStepsWithRust/inline_closure_include.rs fn check_visitor_name(visitor: &Visitor, name: &String) -> bool { return visitor.name == name;}(Wol21,p33) -
let square_c3 = |x: u32| -> u32 { x*x };(Kai17, p141) println!("square of 4 = {}", square_c3(4));- The closure print_add() has one argument and returns a 32 bit integer(Bal17, p94).
- A closure with no arguments has the empty parameter list ||.
- There is also a special kind of closure, called a moving closure, indicated by the move keyword(Bal17, p94).
- A normal closure only takes a reference to the variables it encloses, but a moving closure takes ownership of all the enclosing variables.
- It is self-contained and has its own stack-frame.
- Moving closures are mostly used when your program works with different concurrent threads(Bal17,p95).
- consuming adaptors - Methods that call next are called consuming adaptors, because calling them uses up the iteratorMethods that Consume the Iterator.
-
collect()(Bal17,p97) -
find()(Bal17,p97) - For the rest see the docs of module std::iter
-
- Dynamic dispatch - see dispatch, Dynamic.
- ECS - Entity Component System. managing game data
- Entity can be anything: an adventurer, an orc, or a pair of shoes.
- The game map is usually not an entity, but rather a resource entity’s reference to travel(Wol21,p103)
- Entities don’t have logic associated with them; they are little more than an identification number(Wol21,p103).
- Componnent describes a property an entity may have(Wol21,p104).
- e.g. position, RenderComponent descritption
- Components don’t have logic associated with them(Wol21,p104).
- Systems query the entities and components and provide one element of game-play/world simulation.(Wol21,p104)
- Entity can be anything: an adventurer, an orc, or a pair of shoes.
- dereferencing - see 'Lending' later in the topic(Arb18,p49).
- dispatch, dynamic - (also called runtime polymorphism), using so called trait objects(Bal17,p123).
- e.g.
fn logd(figure: &Figure) {(where 'Figure' is a trait)(Bal17,p123). - used when the precise type on which a function is called can only be known at runtime(Bal17,p124).
- See also: Static and dynamic dispatch
- e.g.
- dispatch, static - at compile time it is resolved which of hte polymorphic instances of a function is called(Bal17,p123)
- e.g. if 'sqroot()' is defined to handle both f32 and f64 as parms
- rust creates two different executable sqroot functions.
- at compile time it is resolved which function gets called, based on the parm type of the call(Bal17,p123).
- With static dispatch, there is a compiled version for each specific type for which the function is called. These calls can be inlined, which guarantees the best performance. On the other hand the binary code size is increased(Bal17,p124).
- e.g. if 'sqroot()' is defined to handle both f32 and f64 as parms
- generic function - (Seems like templating)
fn second<T>(pair: Pair<T>) { pair.second; }- Note the type
<T>right after the function name second; this is how generic functions are declared(Bal17, p100).
- higher order function - The function again is an example of a higher order function, which is a function that takes another function (or more than one) as parameter(Bal17, p93).
- e.g.
fn again<F: Fn(i32) -> i32>(f: F , s: i32) -> i32 { f(f(s)) }
- e.g.
- Generic structs/functions - seems like templates(Bal17,p99)
-
fn second<T>(pair: Pair<T>) {-
second<T>Define the generic function (where 'T' is the generic type) - 'Pair' is a struct previously defined as generic.
-
-
- Heap - a program can request memory space on its heap, which is a much bigger piece of memory than the stack(Bal17,p133).
- Dynamically sized-types, like strings or vectors(Bal17,p133).
- Iterators - An iterator is an object that returns the items of a collection in sequence, from the first to the last(Bal17, p95).
- iterator adaptors - allow you to change iterators into different kinds of iteratorsMethods that Produce Other Iterators.
- Method - a function in an 'impl struct' that has &self as first parameter(Bal17, p118)
- module - a way to organize code within a crate (a Rust package or library).
- Modules help in breaking down the codebase into logical units, making it easier to manage, understand, and maintain.
- Option - Option either contain a value or they don’t(Wol21,p33).
- Rust option are an enumeration(Bal17,p80).
- Option have two possible values:
- Some(x) - in a match you can now operate on 'x'(Wol21,p33).
- None -
- Ownership - Every resource is given a name when we make a binding to it with let; in Rust speak we say that the resource gets an owner(Bal17,p147).
- Reference - you can reference/borrow data as read or as write. See the 'Borrow' section later in this topic.
- Result type - Results are Rust’s standard method for handling errors. Results are an enumeration(Wol21,p51).
- The only requirement to use ? is that your function must return a Result type(Wol21,p51).
- Stack - When a program starts up, by default a 2 Mb chunk of memory called the stack is granted to it. The program will use its stack to store all its local variables and function parameters(Bal17,p133).
- hen our program calls a function, a new stack frame is allocated to it(Bal17,p133).
- When possible, stack allocation is preferred in Rust over heap allocation, because accessing the stack is a lot more efficient(Bal17,p133).
- Static dispatch - see dispatch, static.
- striding - transforming a map location (x,y) into a vector indices(Wol21,p81)
- Target pattern
-
<arch><sub>-<vendor>-<sys>-<env>- arch: x86_64, i386, arm, ...
- sub:
- [example for arm]: v5, v6, v7m
- vendor: [optional] pc, apple, ibm, ...
- sys: none, linux, wind32, darwin
- env: eabi, gnu, elf
-
- Traits - are a way to define shared functionality for objects.
- Traits are similar to interfaces found in other languages, which you use to define a contract(Wol21,p49).
- A trait mostly contains a description of methods, that is, their type declarations or signatures, but no real implementation(Bal17,p117)
- You define the functions that all structs, with this trait, has to implement.
- I guess it is like virtual functions in C++?
- So there is no code body between {} after the function signature(Bal17,p117)
- Rust traits
- Create a new project:
cargo new MY_PROJECT - compile and run an application:
cargo run-
cargo run -- --helpuse '--' to provide cli parms to app that you cargo run(Kol19,p64)
-
- rustup update - update rust
- cargo update - update packages in the project.
- cargo tree - list depencies tree deps tree doc
- When you’re compiling in release mode with the --release flag, Rust does not include checks for integer overflow that cause panics. Instead, if overflow occurs, Rust performs two’s complement wrappingInteger Overflow.
- cargo-edit - add commands like
cargo add- cargo install cargo-edit
- e.g.
cargo add clap postgres- Adds the crate 'clap' and 'postgres'
- cargo add ratatui --features all-widgets
- Adds the ratatui create with
- cargo add opentelemetry_sdk --features metrics,rt-tokio
- cargo-edit source
- commands:
- add - add a crate to Cargo.toml
- rm - remove a crate
- update - update a crate
- cargo-watch - subcommand on cargo. This will monitor the changes made to the files of your project and restart the other commands you have chosen(Kol19,p27).
- cargo install cargo-watch
- cargo watch -x "run"
- You can add extra arguments to the run command between quotes or add extra arguments after the -- characters(Kol19,p28).
[dependencies]
bracket-lib = "~0.8.1"
- The tilde (~) in the version number means: favor version 0.8.1, but accept patches from the 0.8 base.(wol21,p48)
- it seems that it will update last number mentioned in the version number
[dependencies.gtk]
git = "https://github.com/rust-gnome/gtk"(Bal17,p173)
- 1.* means all versions >= 1.0.0 but < 2.0.0
- 1.2.* means all versions >= 1.2.0 but < 1.3.0
- = 1.2.3 means exactly that version
-
= 1.2.3 means bigger than or equal to that version
-
1.2.3 means bigger than that version
- ^1.2.3 means all versions starting from 1.2.3 but < 2.0.0
- ^0 means all versions starting from 0.0.0 but < 1.0.0
- ~1.2.3 means all versions starting from 1.2.3 but < 1.3.0
See: SW Versioning(Bal17p173)
- Get the 'gtk' crate from github
cargo update
opentelemetry-stdout = { version = "0.3.0", features = ["logs"] }
This will update each crate to the latest patch version, e.g. 0.3 to 0.3.4 but update will not update a crate from 0.3 to 0.5
to get the cargo upgrade command You need to install cargo install cargo-edit
cargo new --lib rustwasmhello (Hof19,p48)
[lib]
crate-type = ["cdylib"]This is used when linking WebAssembly modules(Hof19,p48).
const VERSION: &str = env!("CARGO_PKG_VERSION");
const APP_NAME: &str = env!("CARGO_PKG_NAME");
fn main() {
println!("{APP_NAME} {VERSION}");
...
}PACKAGE_NAME=$(shell cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].name')
PACKAGE_VERSION=$(shell cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')
release:
docker build --tag ${PACKAGE_NAME}:${PACKAGE_VERSION} .
See:
# See: https://doc.rust-lang.org/cargo/reference/config.html
[build]
target-dir = "/tmp/rust_build/lunchplan"TODO:
- [cargo-new]
https://github.com/cargo-generate/cargo-generate
- cargo install cargo-generate
TODO
See: Build and run single-file programs
- cargo install cargo-single
- cargo single run single_file.rs
cargo metadata --no-deps --format-version 1 | jq -r '.packages[].targets[].name'cargo metadata --no-deps --format-version 1 | jq -r '.packages[].version'cargo metadata --no-deps --format-version 1 | jq -r '.target_directory'| Use case | Recommended approach |
|---|---|
| Small tool suite | src/bin/ |
| Medium project | Explicit bin entries |
| Microservices / teams | Cargo workspace |
| Monorepo | Workspace + shared libs |
- it is nice to put the function main() at the start of the code to get a better overview.(Bal17,p51)
- Struct names always use camel case()
- Avoid 'use game1::*;'(Bal17,p168)
- makes it harder to see where names are bound(Bal17,p168)
- forward-incompatible, since new upstream exports can clash with existing names.(Bal17,p168)
-
unwrap()- Remove before takeoff- In a production application and adhering to defensive coding practices, we would probably validate this instead of calling unwrap()(Hof19,p58).
TARGET_DIRECTORY := $(shell cargo metadata --format-version 1 | jq | grep target_directory | awk -F\" '{ print $$4 }')- // - line comment
- /_/ - block comment
- /// - Single line documentation
- //! - documentation block start
-
cargo docto generate documentation
There are two types of testing:
-
unit testing: the test code is put in same file as the rust code it is testing.
-
integration testing: tests the public API and that it all works together. Is put in the 'tests' directory.
- The compiler ignores the test functions totally unless it's told to build in test mode. This can be achieved cargo test (which builds and runs the tests)(Kai17,p45).
assert!(a==b);
assert!(a==b, "{} was not equal to {}", a, b);(Kai17,p48)
- The test/directory contains all the integration tests. These are tests that combine the usage of several larger pieces of code together(Kai17,p49).
- These tests are compiled as if they were separate crates(Kai17,p49).
- for an integration test, we need to specify all the crates we are using, even our program's own crate(Kai17,p49).
- Testing documentation code(Kai17,p49).
- Documentation tests are included with your actual code, and come in two forms(Kai17,p50):
- Module-level at the start of the module, marked by //!
- Function-level before a function, marked by ///
- Documentation tests run along with all the other tests when you run a cargo test(Kai17,p51).
- Documentation tests are included with your actual code, and come in two forms(Kai17,p50):
-
cargo test- to run unit tests.- The command cargo test runs tests in parallel whenever possible(Bal17,p60).
-
cargo test - - - -test-threads=1- execute all tests in a single thread(Bal17,p60).
-
cargo test it_works- run the given test(Bal17,p60)
- The single most important Rust performance tip is simple but easy to overlook: make sure you are using a release build rather than a debug build when you want high performance(RustPerfBook).
Link-time optimization (LTO) is a whole-program optimization technique that can improve runtime performance by 10-20% or more, at the cost of increased build times.
[profile.release]
lto = true- println
- {:} - take
- {:?} - raw printing
- {:#?} - prtty print anyt type that supports the debug trait(Wol21,35)
- requires extenstions
- rust-analyzer
- CodeLLLDB
In Rust, you can also write a function inside another function (called a nested function), contrary to C or Java. This should only be used for small helper functions needed locally(Bal17,p59).
- & - reference
- Any assignment that isn’t a reference is a move(Hof19,p54).
- Unless you explicitly treat something as a reference, ownership will be transferred during an assignment, meaning the value in the “old” variable will no longer be there (and you’ll get a compile error if you try to access it)(Hof19,p54).
- array -
- fixed size
- All items must be of the same type(Bal17,p69).
- [0; 1000] - create an array with 1000 elements, all set to 0.
- an unmutable array is stored in one contiguous piece of memory in stack memory(Bal17,p69).
- Pointers to arrays use automatic dereferencing so that you do not need to use * explicitly(Bal17,p70).
- Iteration:
for element in my_array.iter()(Bal17,p96).- This is much more performant and safe than using an index, because now Rust doesn't need to do index-bounds checking, we're always certain to move within the memory of the array(Bal17,p96).
- or:
for element in &my_array(Bal17,p96).
- enum -
- (Bal17,p79)
- enums are sometimes called union types or algebraic data types in other languages(Bal17,p80).
- list -
- Rust ignores any trailing commas at the end of a list(Wol21,p32).
- &str(String slice) - generally used as string literals, which are strings entered in the source code and generally unchanging(Wol21,p27)
- Fixed size: view on string reference(&)(Bal17,p69)
- Module: std::str(Bal17,p69)
- You can use '+' on str(string slices)(Bal17,p67)
- When writing a function that takes a string as argument, always declare it as a string slice, The reason is that passing a string str1 as an argument allocates memory, and passing it as a slice does not allocate(Bal17,p68).
- String - dynamic because they store a location, length, and capacity. You can append Strings and edit them(Wol21,p27).
- Mutable: heap memory allocation(Bal17,p69)
- Module: std::string(Bal17,p69)
- You can use '+' if you have a &str(string slices) as the second part(Bal17,p67)
- vector -
- dynamic size
- is allocated on the heap(Bal17,p71)
- all elements must be of the same size(Bal17,p71)
- vectors are indexed by a variable of type usize(Wol21,p82)
- All elements are the same type.
- i32 - 32 bit signed integer
- isize - integers that take up the same number of bits as a memory address on the target architecture;(Arb18,p31)
- Result -
- Actually an enum(Bal17,p80).
- shadowing - Creating a new variable with the same name as an existing variable(Arb18,p31)
- A shadowed variable still contains the value it did before, but cannot be accessed by name any more, because that name now belongs to a different variable. ???
- str - generally used as string literals, which are strings entered in the source code and generally unchanging(Wol21,p27)
- String - dynamic because they store a location, length, and capacity. You can append Strings and edit them(Wol21,p27).
- tuple - (Arb18,p24)
- a_tuple = (1, "wow", true)`
-
a_tuple.1is "wow" -
(5,)a tuple container only a single value(Arb18,p25)
- type inference - rust infers(guesses) what type the variable should be(Arb18,p32).
- slices - (Bal17,p73)
- You can slice:
- Vectors
- Arrays
- Strings (&str)
- You can slice:
- Struct -
- Tupple structs(Bal17,p77).
- Named fields(Bal17,p78).
- By convention the name of a struct always starts with a capital letter and follows CamelCase(Bal17,p78). CamelCase.
- Tuples - group multiple values, unlike stucts their fields aren't named(Wol21,p93).
- let tuple = (a, b, c);
-
tupple.0is a - A function that needs to return some values of different types can collect them in a tuple and return that tuple(Bal17,p76)
- u64 - 64 bit unsigned integer
- usize - bit size is picked on what the CPU supports/prefers(Wol21,p79).
- does string start with substring?
string.starts_with(substring) - return a formated String:
format!("[INFO]: {}", message) - convert from String to &str:
name.as_str() - Raw strings are prefixed with r or r#. They are printed out literally - new lines or escaped characters are not interpreted(Bal17,p68)
fn drop_doublequote(json_string: &str) -> String {
let mut chars = json_string.chars();
chars.next();
chars.next_back();
chars.as_str()
}TODO see the json spike for how to to this a different way...
A function that can fail will have a return type something like this:
- Result<i32, &'static str>. This looks kind of nuts at first glance, I admit.
- Let's break it down.
- The type starts off with Result followed by a <, then i32, then a ,, then &'static str, and finally a >.
- What that means is that the function will produce an i32 if it succeeds, and an &'static str if it fails.
- &'static str happens to be the type for a literal text expression, like oops, it broke,
- so what we're really saying here is that the function will return an integer or an error message(Arb18,p36).
The simplest way to deal with a Result is to use the ? operator, which extracts the stored value from a successful Result, or returns a Result containing the error value if the Result it's looking at indicates an error. Because ? might return from the current function in the same way that a return statement would, ? can only be used in functions that themselves return Result and use the same data type to represent errors(Arb18,p37). Using ? looks like this:
let mut cons: Constrained = new_constrained(0, 10, 5)?;The next easiest way to deal with a returned Result is to use the expect function.
- This function does something similar to the ?, pulling out the success value if Result indicates success,
- but it handles failure differently.
- Instead of returning an error from the current function, expect terminates the whole program and prints out an error message(Arb18,p38).
Finally, we can actually handle the errors ourselves, by checking whether the returned Result is an Ok or an Err. That is done by using the match or if let expressions, which we will learn about in Chapter 4, the Making Decisions by Pattern Matching(Arb18,p38).
// Bal17, p103
let input_num: Result<u32, _> = buf.trim().parse();
let res = match input_num {
Ok(num) => num,
Err(_) => 0
};Finally, we can actually handle the errors ourselves, by checking whether the returned Result is an Ok or an Err. That is done by using the match or if let expressions, which we will learn about in Chapter 4, the Making Decisions by Pattern Matching(Arb18,p38).
- every data value has a single owning scope(Arb18,p41)
- including implied temporary values such as the result of 2 + 2 when we ask Rust to compute (2 + 2) * 3(Arb18,p41).
- The ownership of a variable follows the same pattern every time: assigning a value to another variable moves itReturn Values and Scope.
- a scope is the place where a block expression stores its variables(Arb18,p41).
- a scope begins when a block expression begins, with a { symbol, and ends when the block expression ends, with } (or when a return statement is run before the block reaches its end)(Arb18,p41).
- The scope is the chunk of memory where the block's variables are stored(Arb18,p41).
- Scopes are not directly represented in the source code(Arb18,p41).
When Rust is done with a scope, all of the data values that scope owns are discarded and the memory that was used to store them is freed up for other uses(Arb18,p41).
In Rust, even when a data value uses heap memory, it is represented on the stack and controlled by the rules of ownership(Arb18,p43).
-
When your code calls a function, the values passed into the function (including, potentially, pointers to data on the heap) and the function’s local variables get pushed onto the stack. When the function is over, those values get popped off the stackThe Stack and the Heap.
-
Move aka. Shallow Copy. But because Rust also invalidates the first variable, instead of calling it a shallow copy, it’s known as a move.
let s1 = String::from("hello");
let s2 = s1;-
Clone aka. deep copy: clone
-
copy vs move
- So what types implement the Copy trait? You can check the documentation for the given type to be sure, but as a general rule, any group of simple scalar values can implement Copy, and nothing that requires allocation or is some form of resource can implement Copycopy vs move.
- The mechanics of passing a value to a function are similar to those when assigning a value to a variable. Passing a variable to a function will move or copy, just as assignment doesOwnership and functions.
- The ownership of a variable follows the same pattern every time: assigning a value to another variable moves it. When a variable that includes data on the heap goes out of scope, the value will be cleaned up by drop unless ownership of the data has been moved to another variableReturn Values and Scope.
(Arb18,p43)
-
seems like if a variable is used as a parm, in a function call, then the variable can no longer be used in the current function(scope)
- the onwership has been transferred to the function called.
- unless the data has the 'Copy' trait(Arb18,p46).
- it seems like interger has the 'copy' trait by default.
-
Only the owner can change the object it points to(Bal17,p147)
- there can only be one owner at a time(Bal17,p147)
- because the owner is also responsible for freeing the object's resources(Bal17,p147).
- there can only be one owner at a time(Bal17,p147)
-
When the owner's lifetime has passed, the compiler frees the memory automatically(Bal17,p147).
-
The owner can move the ownership of the object to another variable like this(Bal17,p148):
let kl2 = klaatu;- the ownership has moved from klaatu to kl2, but no data is actually copied(Bal17,p148).
- The original owner klaatu cannot be used anymore(Bal17,p148)
-
A borrow is a temporary reference, passing the address of the data structure through
&. The borrowed reference can access the contents of the memory location, but does not own the value. The owner gives up full control over a borrowed value while it is being borrowed(Bal17,p149). -
whenever an object goes out of scope and it doesn't have an owner anymore, its destructor is automatically called and the resources owned by it are freed(Bal17,p151).
(Arb18,p47)
-
it is possible to lend immutably to more than one borrower at the same time(Arb18,p47)
-
Borrow
-
borrow_ownership(&main_3);The '&' signifies the borrow/lending- at the receiving end:
pub fn borrow_ownership(point: &Point2D) {(Arb18,p49). - This process is called dereferencing(Arb18,p49)
- at the receiving end:
- We can't lend mutably unless the data value we're lending is stored in a mutable variable, which means that the mut keyword was used when we declared the variable(Arb18,p48)
- like so:
let mut main_4 = main_2; - Given that we have a mutable variable to lend, we can create a mutable borrow of that variable's value, by using the mut keyword again, in a different context:(Arb18,p48)
-
borrow_ownership_mutably(&mut main_4);- at the receiving end:
pub fn borrow_ownership_mutably(point: &mut Point2D) {(Arb18,p49).
- at the receiving end:
-
- like so:
- If there's a mutable borrow of a data value, creating another borrow for it (of either sort) is impossible(Arb18,p48)
- if there are any immutable borrows, then creating a mutable borrow is impossible(Arb18,p48).
- Combined with the rule we discussed earlier that prevents borrowed data from being changed by its real owner, that means that as long as there's a mutable borrow active, that borrow is the only way to modify the borrowed data value(Arb18,p48).
-
- all lifetime names start with the ' symbol(Arb18,p51)(Bal17,p134).
- When we write something such as 'static or 'a after an & symbol, we're telling Rust that the lifetime of that reference has a name, which it recognizes because all lifetime names start with the ' symbol(Arb18,p51).
-
'staticis special/reserved and meand the run time of the application(Bal17,p134)
-
- If we say that a borrow's lifetime is named "
'a," then we can use that name elsewhere to describe the relationship of that lifetime with the lifetimes of other borrows(Arb18,p51). - The static lifetime is special, because it's used for data values that are always available, as long as the program is running(Arb18,p52)
- It's most useful to give names to lifetimes when we're defining functions, because we don't know what data values are going to be filled in to the function's parameter variables(Arb18,p52).
- If some of those parameters are borrows, we need to be able to tell Rust what our expectations are about the lifetimes of those borrows, so it can make sure that the code that calls our functions is doing it correctly(Arb18,p52).
- Specifying a lifetime name never changes the actual lifetime of a borrow(Arb18,p53).
- If value1 and value2 have different lifetimes, specifying 'a for them here doesn't make one of them last longer(Arb18,p53),
- nor does it shorten the span of the other one(Arb18,p53).
- When applied to the parameters, a lifetime name tells Rust that the named lifetime must be compatible with that parameter(Arb18,p53),
- meaning that the named lifetime must be wholly contained within the actual lifetime of the parameter(Arb18,p53).
- Then, when we use the same name for the return value's lifetime, we're telling Rust that the return value will only be guaranteed to be valid within the same limits—in this case, while both of the parameters are still valid(Arb18,p53).
- If value1 and value2 have different lifetimes, specifying 'a for them here doesn't make one of them last longer(Arb18,p53),
- The lifetime of a pointer must always be shorter or equal than that of the value which it points to, thus avoiding dangling (or null) references(Bal17,p135).
struct MagicNumbers<'a> {
magn1: &'a u32,
magn2: &'a u32
}This is to specify that both the struct and the fields have the same lifetime 'a(Bal17,p136).
let main_4 = Point2D {x: 25.0, y: 25.0};
let smaller;
{
let main_5 = Point2D {x: 50.0, y: 50.0};
smaller = smaller_x(&main_4, &main_5);
}
println!("The smaller x is {}", smaller);-
This is a block expression between the { and the },
- which means it has its own scope, which owns the main_5 variable.
- That means that,
- when we create a borrow of main_5, it has a shorter lifetime than
- a borrow of the main_4 variable.
- Rust looks at the function definition for smaller_x and sees that the return value is only guaranteed valid within the lifetimes of both main_4 and main_5, so trying to use it after the block expression has ended produces a compiler error.
-
use with
self(Arb18,p54)- The same rules apply to moving, borrowing, and mutably borrowing the self value as they apply to any other value.
- If we currently have any borrows of a value, we can't mutably borrow it into self, nor can we move it (because that would invalidate the existing borrows).
- If we currently have a mutable borrow of the value, we can't borrow it or move it into self, because mutable borrows do not allow anyone else to borrow or change the value.
- Similarly, borrowing into a function's self affects how we can access the data in other places, because it is a borrow, and there are rules about how borrows coexist.
-
A very common use of functions that consume self is the builder pattern(Arb18,p55).
- The builder pattern is essentially a way to use Rust's syntax to achieve the same things that are achieved by keyword arguments and default values in some other languages(Arb18,p55).
-
Any time the value of self will be invalidated by what the function does, either literally or conceptually, it makes sense to move self into the function's scope(Arb18,p55).
-
self- move? -
&self- immutable borrow. -
&mut self- mutable borrow.
-
-
Struct names always use camel case()
-
Understanding struct, impl, methods and self
- struct: A struct (also called structure), is a custom data type that lets you name and package multiple related values. It starts with the keyword 'struct'.
- impl: An impl is an implementation block that has the keyword "impl" followed by the same name as that of the struct. It is used to comprise methods, and functions. A struct is allowed to have multiple impl blocks.
-
Associated Functions: Associated with the struct(not the instance)
- Referred to by:
structName::associated_function()(Bal17,p112). - Functions inside an impl block that do not take “self” as a parameter are called associated functions because they’re associated with the struct.
- They’re still functions, not methods, because they don’t have an instance of the struct to work with.
- Associated functions are often used for constructors that will return a new instance of the struct.
- new - (not required to be called new, but by convention) used so values can be validated before setting them(Bal17,p110).
- e.g.
let mut berserk = Alien::new(150, 15); - It is a static function because we don't call it on an Alien struct instance(Bal17,p110).
- e.g.
- Referred to by:
-
Methods: called on the struct instance(Bal17,p112).
- Referred to by:
struct_instance.method_name() - The first parameter is always '&self'(bal117.p112).
- Methods are similar to functions, they’re declared with the “fn” keyword followed by their name, they can have parameters and a return value. However, methods are different from functions because they’re defined within the context of a struct and their first parameter is always “self”.
- Referred to by:
-
self: “self” keyword represents the instance of the struct, the method is being called on. “self” when used as the first method argument, is a shorthand for “self: Self”. There are also “&self”, which is equivalent to “self: &Self”, and “&mut self”, which is equivalent to “self: &mut Self”. Self in method arguments is syntactic sugar for the receiving type of the method (i.e. the type whose impl this method is in). This also allows for generic types without too much repetition.
struct Visitor {
name: String,
greeting: String,
}
impl Visitor {
fn new(name: &str, greeting: &str) -> Self {
Self {
name: name.to_lowercase(),
greeting: greeting.to_string(),
}
}
fn greet_visitor(&self) {
println!("{}", self.greeting);
}
}- The
newname is not a mandatory, you can call it anything your want(Bal17,) - Structures can also have associated functions and methods(wol21,p30).
- Associated functions that operate on an instance of the struct are sometimes called methods.
- In Rust,
&selfas a parameter denotes a method that has access to the instance contents(Wol21,p30) - You can’t access new with name.new(); instead, it’s available in the structure’s namespace. You can call it with Visitor::new()(Wol21,p31).
- In Rust,
- The variable ;struct has to be declared as mutable if the field values can change, for example, when the player enters a new level
- defining struct
- What is the trait used for?, couldn't i simply achieve the same result simply by defining a summarize() function for each struct, or does the compiler prevent that???
- analogous to an interface or superclass in other languages(Bal17,p117).
- A trait can have multiple methods in its body: the method signatures are listed one per line and each line ends in a semicolon.
- A trait can provide default code for a method(Bal17,p118)
- The implementor type can choose to take this default code or override it with its own version(Bal17.p119).
- Traits are not limited to structs, they can be implemented on any type(Bal17.p119).
- A type can also implement many different traits(Bal17.p119).
- One restriction to note is that we can implement a trait on a type only if at least one of the trait or the type is local to our crate.
- For exampleImplementing a Trait on a Type,
- we can implement standard library traits like Display on a custom type like Tweet as part of our aggregator crate functionality, because the type Tweet is local to our aggregator crate.
- We can also implement Summary on
Vec<T>in our aggregator crate, because the trait Summary is local to our aggregator crate.
- For exampleImplementing a Trait on a Type,
- Traits can also inherit from other traits, indicated with the ':' operator:
trait Trait1 : SuperTrait(Bal17,p127) - The compiler will enforce that any type that has the Summary trait will have the method summarize defined with this signature exactly.
- how is 'Summary' references? what is 'Summary' used for?
- users of the crate can call the trait methods on instances of NewsArticle and Tweet in the same way we call regular methods.
- The only difference is that the user must bring the trait into scope as well as the types
use aggregator::{Summary, Tweet};
- You can create a default Trait
pub trait Summary {...}- A type can use the complete default trait by doing
impl Summary for NewsArticle {}; - Creating a default trait would allow the default trait to implement details on how to get specific information from the type
- e.g. the Default could implement the format of the summary and then
get_author()could be implemented in the impl for the type
- e.g. the Default could implement the format of the summary and then
- seems a bit like base clases and virtual functions in C++
- is this related to 'Protocol' in elixir?
- A type can use the complete default trait by doing
- Traits as Parameters:
pub fn notify(item: &impl Summary) { println!("Breaking news! {}", item.summarize());}- This meansh that any type that implements 'Summary' can be the parameter to
notify() - the parm will be the an instance of the type. e.g.
notify(tweet); - Traits as Parameters
- This meansh that any type that implements 'Summary' can be the parameter to
- Trait bound syntax:(a more verbose version of 'Traits as Parameters'?)
pub fn notify<T: Summary>(item: &T) {println!("Breaking news! {}", item.summarize());}- I guess 'T' is set to the 'Summary' trait
- and then 'item' is defined as a reference to 'Summary'
- I wonder why 'impl' is not mentioned???
- Trait Bound Syntax
- Multiple Trait bounds:
pub fn notify(item: &(impl Summary + Display)) { - using
whereto make it easier to read- Clearer Trait Bounds with where Clauses
- replace:
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {with the below code
fn some_function<T, U>(t: &T, u: &U) -> i32
where T: Display + Clone,
U: Clone + Debug
{- Returning Types that Implement Traits:
fn returns_summarizable() -> impl Summary {- Returning Types that Implement Traits
- However, you can only use impl Trait if you’re returning a single type. For example, this code that returns either a NewsArticle or a Tweet with the return type specified as impl Summary.
- Returning either a NewsArticle or a Tweet isn’t allowed due to restrictions around how the impl Trait syntax is implemented in the compiler.
trait Monster {
// (purely virtual)
fn attack(&self);
// (default behaviour.)
fn attacks_with_sound(&self) {
println!("The Monster attacks by making an awkward sound {}",
self.noise());
}
}
#[derive(Debug)]
struct Alien { health: u32, damage: u32 }
impl Monster for Alien {
fn attack(&self) {
println!("I attack! Your health lowers with {} damage points.",
self.damage);
}
}TODO understand and describe the above code
Source: (Bal17,p118)
fn sqroot<T: num::traits::Float>(r: T) -> Result<T, String> {...}- The
<T: num::traits::Float>- This is called putting a 'trait constraint' or a 'trait bound' on the type T(Bal17,121).
- We assert that type T implements the trait num::traits::Float, and it ensures that the function can use all methods of the specified trait.
Another way to do that: fn sqroot<T>(r: T) -> Result<T, String> where T: Float { ... } (Bal17,122).
-
An iterator is an object that returns the items of a collection in sequence, from the first to the last(Bal17, p95).
- To return the following item, it uses a next() method(Bal17, p95).
- Here, we have an opportunity to use an Option(Bal17, p95).
- Because an iterator can have no more values at the last next() call, next() always returns an Option(Bal17, p95),
- Some(value) when there is a value(Bal17, p95) and
- None when there are no more values to return(Bal17, p95).
- Because an iterator can have no more values at the last next() call, next() always returns an Option(Bal17, p95),
-
iterators are lazy - an iterator does nothing by itself, it has to be called in order to produce somethingMethods that Produce Other Iterators
- .next() provide next item
- use in
forloop - .collect() - This method consumes the iterator and collects the resulting values into a collection data type.
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
-
The Iterator Trait and the next Method
- when doing
v1_iter.next()we needed to make v1_iter mutable:- calling the next method on an iterator changes internal state that the iterator uses to keep track of where it is in the sequence.
- Each call to next eats up an item from the iterator.
- v1_iter does not have to be made mutable when used in a for loop, since that will be done behind the scene
- the loop takes ownership of v1_iter and make it mutable behind the scenes.
- when doing
-
0..7 is an iterator.
-
.iter() - provides an ummutable reference
- e.g. let v1_iter = my_var.iter();
- the iter() method returns an iterator object of the collection.
-
.into_iter() - provide owned values.
-
.iter_mut() - provides mutable references.
-
consuming adaptors - Methods that call next are called consuming adaptors, because calling them uses up the iterator.
-
The function collect() loops through the entire iterator, collecting all of the elements into a container(Bakl17,p97)
-
iterator operations See: Iterator trait
- .sum()
- .filter()
- .find() - is short-circuiting; stop processing as soon as the closure returns true.
- .iter() - iterates over &T see std::iter
- iter_mut() - iterates over &mut T.
- into_iter() - iterates over T.
- .map()
- .next()
- .rev()
let aliens = ["Cherfer", "Fynock", "Shirack", "Zuxu"];
for alien in aliens.iter() {
print!("{} / ", alien)
} // outputs: Cherfer / Fynock / Shirack / Zuxu /- This is much more performant and safe than using an index, because now Rust doesn't need to do index-bounds checking, we're always certain to move within the memory of the array(Bal17, p96).
Shorter method(Bal17, p96):
for alien in &aliens {
print!("{} / ", alien)
} loop {
match args_iterator.next() {
Some(parameter) => {
match parameter.as_str() {
"--output-file" => println!("XXX"),
_ => println!("EEE unknown option: '{}'", parameter)
}
},
None => { break }
}
}fn evens<T>(iter: impl Iterator<Item = T>) -> impl Iterator<Item = T> { ... }-
T - this is the type, e.g.
i32or a struct. -
Iteratorwhat is this?- Maybe 'Iterator' is a trait??? (but what the
<Item = T>means, I do no know...) - Random function name?
- Special name? that has special meaning?
- Maybe 'Iterator' is a trait??? (but what the
-
<Item = T>what on earth does this do?- Does it set 'Item' to 'T' but why not just have
<T>then???
- Does it set 'Item' to 'T' but why not just have
-
A composite data structure is generic if the type of its items can be of a general type
<T>.- The type T can for example be an i32 value, an f64, a String, but also a struct type like Person that we have coded ourselves. So, we can have a vector
Vec<f64>, but also a vectorVec<Person>. - If you make T a concrete type, then you must substitute the type T with that type everywhere T appears in the definition of the data structure.
- Our data structure can be parametrized with a generic type
<T>, and so has multiple concrete definitions--it is polymorphic. - Rust makes extensive use of this concept, which we encountered already in Chapter 4, Structuring Data and Matching Patterns when we talked about arrays, vectors, slices and the Result and Option types.
- The type T can for example be an i32 value, an f64, a String, but also a struct type like Person that we have coded ourselves. So, we can have a vector
// see code in Chapter 5/code/generics.rs
struct Pair<T> {
first: T,
second: T,
}let arr = [1.0, 2.5, 3.14];let arr: [f64;3] = [1.0, 2.5, 3.14];let weekdays: [&str:7] = ['Monday', 'Tuesday', 'Wednesday', 'Thusday', 'Friday', 'Saturday', 'Sunday'];
for day in &weekdays {
println!("=== {}", day);
} let carb_options = ["2 dark bread", "2 dl rice", "1 bagel", "1 tortilla", "3 dl pasta", "2 pancakes", "4 potatpes egg size", "15 ps. pommes frites"];
let mut rng = rand::thread_rng();
match carb_options.choose(&mut rng) {
Some(item) => println!(" {}", item),
None => println!("!!! Unexpected resutl from random selection")
}Can hold mix if types
-
using vectors
-
adding to vector
-
returning a vector
-
what happens to the content that is added to a vector?
-
Create a vector
let mut my_vector:usize = Vec::new();
-
iterate through vector
-
the semicolon tells Rust that the expression before it should be treated as a statement(Arb18,p25),
- which basically means that it won't produce a value, or if it does, we don't care what that value is(Arb18,p25).
- In some cases, the semicolon can be left off of expressions prior to the last one in a block, but I don't recommend it, because explicitly discarding the results of expressions whose results we're not going to use allows the compiler more freedom to make inferences and optimizations, and can help avoid some fairly obscure compiler errors(Arb18,p25).
- If we put a ; after the final expression in a block, we're saying that the block doesn't have a meaningful resulting value at all. In that case, it ends up having () as its resulting value(Arb18,p26).
- That's an empty tuple, which is a pretty good way of saying: Nothing to see here, folks. () is used that way throughout the Rust language and libraries(Arb18,p26).
-
if(Arb18,p26)
if my_value > 4 {
println!("value {} is greater than four.", my_value);
}
else if my_value == 4 {
println!("value {} is four.", my_value);
}
else {
println!("value {} is less than four.", my_value);
};- match(switch/case)
match number {
// Match a single value
1 => println!("One!"),
// Match several values
2 | 3 | 5 | 7 | 11 => println!("This is a prime"),
// Match an inclusive range
13...=19 => println!("A teen"),
// Handle the rest of cases
_ => println!("Ain't special"),
// TODO ^ Try commenting out this catch-all arm
}For match on enums, you have to include the Enum name The match Control Flow Construct
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,
}
}-
while(Arb18,p27)
-
for(Arb18,p27)
- A for loop runs its block expression once for each value produced by an iterator(Arb18,p28).
for num in 3..7 {
println!("for loop {}", num);
}3..=7 will include the 7
-
- rust module function
-
A module without a body is loaded from an external file.
mod something;
-
self::a function in the current module itself(Bal17,p168) -
super::a function in the parent scope outside of the module(Bal17,p168) -
The
usestatements are preferably written at the top of the code file, so that they work for the whole of the code(Bal17,p168). -
pub use game1::{func2, func3};export these definitions (in other words, you make them visible) to the super level(Bal17,p168). -
mod modul1;- In Rust, we insert the entire contents of a module source file into the current file by declaring the module at the top of the code (but after any use statements)(Bal17,p168).- This statement will look for a file, modul1.rs, in the same folder as the current source file, and import its code in the current code inside a module,
modul1(Bal17,p168). - If a modul1.rs file is not found, it will look for a mod.rs file in a subfolder modul1, and insert its code(Bal17,p168).
- This statement will look for a file, modul1.rs, in the same folder as the current source file, and import its code in the current code inside a module,
-
extern crate(Bal17,p172)- Think of extern crate as a declaration inside your code that tells the compiler you want to link to another crate so you can use its functionality, kinda like the -l argument for clang or gcc. Whereas the [dependencies] section in Cargo.toml will orchestrate the downloading and building of your dep...Usage of
extern crate?- XXX Is this for internal crates? (created with this source code?)
- Think of extern crate as a declaration inside your code that tells the compiler you want to link to another crate so you can use its functionality, kinda like the -l argument for clang or gcc. Whereas the [dependencies] section in Cargo.toml will orchestrate the downloading and building of your dep...Usage of
- A module as a section of a file(Arb18,p19)
-
pub mod module_a {- The pub keyword means that module_a is part of the public interface of the current module(Arb18,p19).
-
- A module as a separate file(Arb18,p19)
-
pub mod module_b;- this is used in the file that wants to access the module.
- the compiler will look for either of:
module_b.rsmodule_b/mod.rs- It is encouraged to use the
module_b.rsnaming convention as it is more consistent, and avoids having many files named mod.rs within a project.Module Source Filenames
-
- src/map.rs
pub struct Map {
svg_base_unit: isize,
}
impl Map {
pub fn new() -> Map {
Map {svg_base_unit: 0}
}
// TODO
pub fn render() {
}
}- main.rs
mod map;
use map::Map;-
accessing using Full name
-
module_a::a_second_thing();(Arb18,p20) std::path::Path()
-
-
using short name(Arb18,p21)
-
use std::path::Path;- You can now access the function using:
Path();
- You can now access the function using:
- The use lasts until the closing '}' or the end of the module.
-
-
Modules are organized as a tree(Wol21,p79).
-
When you import access to other modules with use, you have several ways of accessing different parts of the module tree(Wol21,p79).
- super:: accesses the module immediately above your module in the tree(Wol21,p79).
- crate:: accesses the root of the tree, main.rs(Wol21,p79).
items are functions, structures etc.
-
if it has
pubin front of it, it is accessible outside the module. -
if not, it is only accessible inside the module.
- private is default.
-
The private/public only shows intent, a private function in not protected in any way. We just don't how to provide any API stability to private items(Arb18,p21).
Things in modules are by default private(Wol21,p77).
-
modules branching from crate are visible throughout your program(Wol21,p77).
-
Publicly using the bracket_lib prelude re-exports it inside your prelude. Anything that uses your prelude also uses bracket_lib(Wol21,p77).
- TODO go into understanding and describing the
preludething
- TODO go into understanding and describing the
-
Adding pub to constants makes them public. Declaring them in your prelude makes them available to any section of code that uses your prelude(Wol21,p77).
-
Modules are organized as a tree(Wol21,p79).
- When you import access to other modules with use, you have several ways of accessing different parts of the module tree(Wol21,p79).
- super:: accesses the module immediately above your module in the tree(Wol21,p79).
- crate:: accesses the root of the tree, main.rs(Wol21,p79).
- When you import access to other modules with use, you have several ways of accessing different parts of the module tree(Wol21,p79).
-
for i in 0..10 { println!("{}", i) }- loop from 0 to 9(Wol21,p28)
-
0..=10loop 0 to 10
Iterate an array(Wol21,p28)
for visitor in &visitor_list {
if visitor == your_name { ... }
}'&' means “borrow a reference.”
(Wol21,p32)
- Create an iterator with iter() that contains all of the data from the list(Wol21,p32).
- find() runs a closure. If the closure returns true, find() returns the matching value. The semicolon finishes the statement.
- Iterators are also very fast, often faster than writing equivalent loops yourself(Wol21,p34).
- The compiler can be certain that you aren’t trying dangerous operations such as reading beyond the end of an array. It’s able to make a number of optimizations as a result(Wol21,p34).
-
#[derive(Copy, Clone, PartialEq)]
- Clone - adds a clone() function to the type(Wol21,p80).
- Calling mytile.clone() makes a deep copy of the variable without affecting the original.
- If you clone a struct, everything the struct contains will also be cloned.
- Copy - changes the default action when assigning a TileType from one variable to another(Wol21,p81). * Instead of moving the value, it takes a copy. Smaller types are often faster when you copy them around.
- PartialEq - adds code that allows you to compare TileType values with the == operator(Wol21,p81).
- Clone - adds a clone() function to the type(Wol21,p80).
-
#[derive(Debug)]- Derive macros are a very powerful mechanism to save you from typing repetitive boilerplate code(Wol21,p35).- Once you’ve derived Debug, you can use the println! placeholder {:?} to print your entire struct(Wol21,p35).
-
#[system]-- Using systems automatically turns your game into a multi-threaded program(Wol21,p117).
Source:
- (Wol21,p33)
- https://doc.rust-lang.org/std/option/
match known_visitor {
Some(visitor) => visitor.greet_visitor(),
None => println!("You are not on the visitor list. Please leave.")
}- the macro expands the original code into new code(Bal17,p177).
- This expansion happens early in compilation, before any static checking is done, so the resulting code is compiled together with the original code(Bal17,p177).
- So, as a Rust developer, you can also write your own macros, replacing repetitive code with much simpler code and thereby automating tasks(Bal17,p177).
- Macro code itself is harder to understand than normal Rust code, so it is not that easy to make(Bal17,p177).
macro_rules! mac1 {
(pattern) => (expansion);
(pattern) => (expansion);
...
}- the definition of a macro is also done through a macro;
macro macro_rules!(Bal17,p177)- a macro is similar to a match block, defining one or more rules for pattern matching, each rule ending on a semicolon(Bal17,p177).
- The macro can be called as either:
mac1!()mac1![]- the braces contain the arguments(Bal17,p177).
macro_rules! welcome {
() => (
println!("Welcome to the Game!");
)
}-
Parameters - see (Bal17,p179)
-
Debugging macros(Bal17,p182)
#![feature(trace_macros)]
#![feature(log_syntax)]- Using macros from a crate(Bal17,p182)
#[macro_use(mac1, mac2)]
extern crate abc;- only macros defined with the
#[macro_export]attribute can be loaded in another module(Bal17,p182).
- How to install rust
- sudo apt install build-essential
- ~/.rustup - Rustup metadata and toolchains will be installed into this directory.
- This can be modified with the RUSTUP_HOME environment variable.
- ~/.cargo -
- This can be modified with the CARGO_HOME environment variable.
- ~/.cargo/bin - The cargo, rustc, rustup and other commands will be added here
- rustup toolchain list
- rustup target list
-
rustc main.rs --pretty=expanded- show expanded macros
- rustup update
- println! is a macro that prints text to the console.
- {:?} the ':?' - Replacing the {} placeholder with {:?} uses the debug placeholder. Any type that supports debug printing will print a detailed debugging dump of its contents(Wol21,p25)
- a line that doesn’t end with a semicolon is Rust shorthand for return(,p25).
- Any expression may return this way. It’s the same as typing return your_name;.
- Clippy will complain if you type return when you don’t need it.
- let - TODO
- Rust allows us to use multiple let statements to create new variables with the same names as old variables, but we can't just assign a new value to an existing variable, unless that variable is mutable(Arb18.p30).
- unwrap() - seems to be a chain of
ok().expect()(Bal17,p83)- if the result is Ok() return the value inside, otherwise panick.
- Do not use in production.
- See also expect(), which does the same but you can customize the panick message.
- This unwraps a value Result, yielding the content of a value Ok (or of a value Some in the case of a value Option), but panics if the value is an Err (or a None for Option), with a panic message provided by the Err's value(Bal17,p83).
- unwrap_or(X) return X when the result is not Ok()
-
..- exclusive range, used infor in(Bal17,p86) -
...- inclusive range, used inmatch(Bal17,p86) -
unimplemented!();- use as a placeholde in code; the code compiles but panics at run-time(Bal17,p183).
fn again<F: Fn(i32) -> i32>(f: F , s: i32) -> i32 {
f(f(s))
}- The expression between < > (angle brackets) tells us that F is of type function Fn that takes an i32 as parameter and returns an i32 value(Bal17, p93).
It seems:
-
Fis the var for the type(macro) -
Fnis that code for 'fn' aka function, or is the 'F' of the 'Fn' related to theFn? -
What does the
< >mean? what are they called? -
Ending an expression with a semicolon like a + b; suppresses this behaviour, thus throwing away the return value and making it an expression statement returning the unit value
()(Bal17,p40). -
Primitive values as numbers (like 32 in the figure), characters, or true or false values are stored on the stack, but the value of more complex objects that could grow in size are stored in heap memory. Heap values are referenced by a variable on the stack, which contains the memory address of the object on the heap(Bal17,p4x).
-
A function that never returns is called a diverging function and it has return type !(Bal17,p53).
-
You can have a function as a parameter of another function. (Aka Higher order functions)
-
fn again<F: Fn(i32) -> i32>(f: F , s: i32) -> i32 { f(f(s)) }(Bal17,p93)-
Fis a type (defined as 'Fn(i32)') Fn- The expression between < > (angle brackets) tells us that F is of type function Fn that takes an i32 as parameter and returns an i32 value(Bal17,p93).
-
-
-
Compared to other languages, you will see that there is a tendency in Rust to move functionalities out of the core language into their own crates. It serves to keep the standard library small, thereby reducing the size of compiled apps(Bal17,p227).
-
#[derive(Debug)]and set the#![deny(missing_debug_implementations)]attribute for the whole crate(Fol18,p54).- Find out how to use the
#[derive(Debug)]
- Find out how to use the
-
XXX TODO Make a list of a2 = mut a1 etc for each compbination and list who can acces what and how
- and also call to functions (Bal17,p156)
- See also 'Overview of pointers'(Bal17,p159)
-
try!(Bal17.p219) -
#![no_std]- Using Rust without the Standard Library(Bal17,p225) -
Hasmap - (bal17,p214)
-
De-reference -
* -
Pointers - A pointer is a variable that contains the memory address of some value(Bal17,p141).
- To access the value it points to, dereference the pointer with
*(Bal17,p141).- This happens automatically in simple cases like in println! macro or when a pointer is given as a parameter to a method(Bal17,p141).
- The compiler automatically takes care of the memory allocation of pointers, and the freeing up of memory when their lifetime ends(Bal17,p142).
- When passing a pointer value to a function, it is always better to use the reference-dereference &* mechanism(Bal17,p142).
- To access the value it points to, dereference the pointer with
-
Reference -
&or borrowed pointer(Bal17,p143). -
in order to change a value through a reference, both the variable and its reference have to be mutable, like in the following code snippet(Bal17,p144):
- But note that we now cannot change (or even print) that value anymore by using the variable u(Bal17,p144)
- We say that borrowing a variable (by taking a reference to it, here v) freezes that variable(Bal17,p144)
- the original variable u is frozen (no longer usable), until the v reference goes out of scope(Bal17,p144).
- we can only have one mutable reference(Bal17,p145).
- This is logical--the compiler is concerned that a change to the value of u through one reference might change its memory location(Bal17,p145).
- because the variable u might change in size (in case of a more complex variable than an integer) and so not fit anymore in its previous location, and would have to be relocated to another address(Bal17,p145).
- This would render all other references to u invalid, and even dangerous, because through them we might inadvertently change another variable that has taken up the u variable's previous location!(Bal17,p145)
let mut u = 3.14f64;
let v = &mut u;
*v = 3.15;- References are frequently used as function parameters: they avoid moving (which means in most cases copying) the value to the function, which could decrease performance when the value is large. Instead passing a reference only copies the address of the value to the function, not the value, thereby saving unnecessary memory usage(Bal17,p145).
let mag = Magician { name: "Gandalf", power: 4625};
let name = {
let Magician { name: ref ref_to_name, power: _ } = mag;
*ref_to_name
};
println!("The magician's name is {}", name);- After the moving closure is defined, the variable(s) reference in the closure can no longer be accessed(Bal17,152).
- a pointer type
Box<T>, A box is a non-copyable value. This pointer type is used to allocate objects on the heap(Bal17,p153).
(Bal17,p157)
See: Saving the Argument Values in Variables
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
let query = &args[1];
let file_path = &args[2];
println!("Searching for {}", query);
println!("In file {}", file_path);
}- use the cargo 'libc'(Bal17,p207)
- Though it might be enough with
#![feature(libc)]now(Bal17,p208)
- Though it might be enough with
- you will have to use the Foreign Function Interface (FFI); its utilities are in the std::ffi module(Bal17,p208).
- The signatures of the C functions we want to use must be listed in an extern{} block(Bal17,p210).
- The compiler cannot check these signatures, so it is important to specify them accurately to make the correct bindings at runtime(Bal17,p210).
- This block can also declare global variables exported by C to be used in Rust(Bal17,p210).
- These must be marked with static or static mut, for example, static mut version: libc::c_int(Bal17,p210)
- The extern block must be preceded by a #[link(name = "m")] attribute to link in the libm library(Bal17,p210).
- This instructs rustc to link to that native library so that symbols from that library are resolved(Bal17,p210).
- Rust code can be called from any language that can call C. The Rust library should have the crate-type value "dylib"(Bal17,p211)
-
Future - trait that returns a result in the future and represents an operation that can't be completed immediately(Kol19,p96).
- has two outcome variants that are represented by the associated types Item and Error(Kol19,p96).
- has a poll method, which is what retrieves the result.
- This method will be called by a reactor until it returns Error or an Async::Ready value(Kol19,p96)
- Ready means the value is ready to use,
- Pending means the value is not yet available and will be ready later.
- This method will be called by a reactor until it returns Error or an Async::Ready value(Kol19,p96)
- Future is used for a similar purpose to Result.
- Unlike Result, however, Future is a trait, which means the implementation is not specified and many types can implement it.
- A useful feature is the FutureExt trait which can be implemented for all the Future instances.
-
mpsc - multi-producer single-consumer(Kol19, p94)
-
ordering - (Bos23,p 52)
- It seems like it refers to the order of execution, possibly between threads?
- relaxed memory ordering - there are no other happens-before relationships(Bos32,52)
- While atomic operations using relaxed memory ordering do not provide any happens-before relationship, they do guarantee a total modification order of each individual atomic variable(Bos32,54).
- This means that all modifications of the same atomic variable happen in an order that is the same from the perspective of every single thread(Bos32,54).
-
reactive -
-
reactor -
-
reunite - reunite a SplitSink and SplitStream, created by
split(Kol19, p98) -
spsc - Single-Producer Single-Consumer(Kol19, p98)
-
sink - sending data. similar to Stream, but works in the opposite direction.
- It contains two associated types—SinkItem and SinkError.
-
split - get an instance of Sink attached to a stream. This call returns a tuple with both SplitSink and SplitStream objects(kol19, p98).
-
stream - receiving data; a trait that represents a sequence of deferred items.
- It works in a similar way to the Iterator trait, but it uses the poll method to get the next Item or to return Error in the case of failure(Kol19,p97).
- The stream can either be incoming data from a socket or data that can be read from a file.
- Stream can be converted to Future and vice versa if the Future instance returns a Stream.
-
Rust implements asynchronous programming using a feature called async/await
- Functions that perform asynchronous operations are labeled with the async keyword.
- It seems that the function marked with
asyncarent executed until we callawaiton the async function. - Any calls to .await within the async fn yield control back to the thread.
- The return value of an async fn is an anonymous type that implements the Future trait.
-
An async fn is used as we want to enter an asynchronous context. see
- However, asynchronous functions must be executed by a runtime.
- The runtime contains the asynchronous task scheduler, provides evented I/O, timers, etc.
- The runtime does not automatically start, so the main function needs to start it.
- The #[tokio::main] function is a macro. It transforms the async fn main() into a synchronous fn main() that initializes a runtime instance and executes the async main function.
-
Tasks are the unit of execution managed by the scheduler.
- Spawning the task submits it to the Tokio scheduler, which then ensures that the task executes when it has work to do.
- The spawned task may be executed on the same thread as where it was spawned, or it may execute on a different runtime thread.
- The task can also be moved between threads after being spawned.
- Tokio tasks are very lightweight, they require only a single allocation and 64 bytes of memory.
- spawned task must not contain any references to data owned outside the task.
- current_thread runtime flavor is a lightweight, single-threaded runtime.
-
data shared between tasks must be shared using synchronization primitives such as Arc.
-
Send -
- Tasks are Send when all data that is held across .await calls is Send.
- If a variable is created berfore
.await()and used after then the variable must have the same persistance as the task.- I'm guessing with the
'staticconcept???
- I'm guessing with the
- If a variable is created berfore
- Tasks are Send when all data that is held across .await calls is Send.
-
Share data between tasks
- Guard the shared state with a Mutex.
- used for simple data
- Spawn a task to manage the state and use message passing to operate on it.
- for things that require asynchronous work such as I/O primitives.
-
Arc - Atomically Reference Counted.
- When shared ownership between threads is needed, Arc can be used.
- This struct, via the Clone implementation can create a reference pointer for the location of a value in the memory heap while increasing the reference counter.
- std::sync::Mutex - synchronous mutex. Will block the current thread when waiting to acquire the lock.
- This, in turn, will block other tasks from processing.
- TODO how?
- As a rule of thumb, using a synchronous mutex from within asynchronous code is fine as long as contention remains low and the lock is not held across calls to .await
- tokio::sync::Mutex - async mutex. A mutex that is locked across calls to .await. See
- switching to tokio::sync::Mutex usually does not help as the asynchronous mutex uses a synchronous mutex internally.
- If contention on a synchronous mutex becomes a problem, consider:
- Switching to a dedicated task to manage state and use message passing.
- Shard the mutex.
- Restructure the code to avoid the mutex.
- Guard the shared state with a Mutex.
-
Sync - Types that can be concurrently accessed through immutable references are Sync.
-
operations/calls
- The select! macro can handle more than two branches. The current limit is 64 branches.
- wake
- poll
- spawn
- await
-
foo().awaitvsfoo().await?- foo().await? - propagate any error that might occur. This is useful when foo returns a Result<T, E>
- If the result is Ok(T), it unwraps the Ok variant and yields T.
- If the result is Err(E), it returns the error E from the current function, propagating the error to the caller.
- foo().await? - propagate any error that might occur. This is useful when foo returns a Result<T, E>
-
async fn example() -> Result<(), String> {
let value = foo().await?;
println!("Value: {}", value);
Ok(())
}async fn example() {
match foo().await {
Ok(value) => println!("Value: {}", value),
Err(e) => println!("Error: {}", e),
}
}-
TODO what does this code mean:
(Executor { ready_queue }, Spawner { task_sender }) -
when using a multithreaded Future executor, a Future may move between threads. this means that it is not safe to use Rc, &RefCell or any other types that don't implement the Send trait, including references to types that don't implement the Sync trait..awaiting on a Multithreaded Executor
- it isn't a good idea to hold a traditional non-futures-aware lock across an .await, as it can cause the threadpool to lock up
-
TODO It seems that if a struct has a reference or is being referenced? then the struct being references has to be Pinned, for the reference to stay valid throughout the lifetime.
- If a struct do not need to be pinned it is marked as
UnpinPinning in Practice - types that can't be moved after they're pinned have a marker called !Unpin. Futures created by async/await is an example of this.
- Pinning an object to the stack will always be unsafe if our type implements !Unpin.
- I guess it is called 'unsafe' because it can't be guranteed to hold up, as described below(e.g. panics)
- Unsafe because with an 'Unpin' mark we now assume this is solid, but in reality it is not solid, just an intent to keep it solid...?
- For types that implement !Unpin, moving the value after pinning is unsafe because it can invalidate internal references or assumptions about memory layout.
- When you pin an object to the stack, it resides in a local stack frame.
- Local stack variables have lifetimes tied to their scope, and their memory can be reclaimed or reused after the scope exits.
- Rust's type system cannot guarantee that the address of a stack-allocated value will remain stable because the stack frame can be unwound (e.g., when a function returns or panics).
- Moving a pinned value from the stack to another location (e.g., by returning it from a function or by reassigning it) violates the pinning guarantee and can lead to undefined behavior.
- I guess it is called 'unsafe' because it can't be guranteed to hold up, as described below(e.g. panics)
- If a struct do not need to be pinned it is marked as
-
A mistake that is easy to make is forgetting to shadow the original variable since you could drop the Pin and move the data after &'a mut T
-
Pinning an !Unpin type to the heap gives our data a stable address so we know that the data we point to can't move after it's pinned. In contrast to stack pinning, we know that the data will be pinned for the lifetime of the object.Pinning to the Heap
- To use a Future or Stream that isn't Unpin with a function that requires Unpin types, you'll first have to pin the value using either
Box::pin (to create a Pin<Box<T>>)or the pin_utils::pin_mut! macro (to create a Pin<&mut T>).Pin<Box<Fut>>andPin<&mut Fut>can both be used as futures, and both implement Unpin.
- To use a Future or Stream that isn't Unpin with a function that requires Unpin types, you'll first have to pin the value using either
-
Pinning a !Unpin object to the stack requires unsafe
-
Pinning a !Unpin object to the heap does not require unsafe. There is a shortcut for doing this using Box::pin.
-
the FusedFuture trait is required because select must not poll a future after it has completed. FusedFuture is implemented by futures which track whether or not they have completed.Interaction with Unpin and FusedFuture
- As a rule, channels are a one-way interaction primitive.
- A channel has a sender to send messages and a receiver to extract messages.
- Internally, a channel works as an array or list that is protected from data races (when two or more threads try to write the same memory cell) using an atomic flag or lock-free data types(Kol19,p98).
-
Tokio channels
- broadcast: multi-producer, multi-consumer. Many values can be sent. Each receiver sees every value.
- mpsc: multi-producer, single-consumer channel. Many values can be sent.
- oneshot: single-producer, single consumer channel. A single value can be sent.
- completes immediately and does not require an .await. This is because send on a oneshot channel will always fail or succeed immediately without any form of waiting.
- watch: single-producer, multi-consumer. Many values can be sent, but no history is kept. Receivers only see the most recent value.
- The channel is created with a capacity of 32.
- Once the 32 messages are stored in the channel, calling send(...).await will go to sleep until a message has been removed by the receiver.
-
Allocating a buffer
- A stack buffer is explicitly avoided.
- Because of this, it is usually more efficient to use a dedicated allocation for the buffer.
- Forgetting to break from the read loop usually results in a 100% CPU infinite loop situation.
- Forgetting to wake a task after returning Poll::Pending is a common source of bugs.
Rust’s borrow/lending system makes it easy to share data. It is always tempting to store the borrowed data in case you need it later. Rust’s borrow checker makes your life difficult when you do this, it has to be able to prove that no unsafe behavior occurs. Keeping your borrows as short as they can be prevents this frustration, and also avoids a whole class of common bugs that Rust was designed to prevent(Wol21,p132).
-
Working with files(Bal17,p216)
- Path(Bal17,p216)
use std::{fs::File, os::unix::prelude::FileExt};
use std::io::Write;
let mut file_handle = File::create("test.txt").expect("Error encountered while creating file!");
file_handle.write(b"Hi, Welcome to Rust Programming!").expect("Error while writing to file"); file_handle.write(&format!(" width=\"{}\" height=\"{}\"\n", map_width, map_height).as_bytes()).expect("file write error");-
Minidom requires: an
xmlns=""present, at least on the root Element. -
Mindom will fail if it sees
<!--, even if that is inside a valid<comment></comment>tag set (hmmm) -
cargo add minidom
Example:
use minidom::Element;
use std::fs;
const DEFAULT_NAME_SPACE: &str = "minidom";
fn main() {
let xml_string = fs::read_to_string("test_lab.xml").expect("File not found");
println!("DDD {}", xml_string);
let xml_root: Element = xml_string.parse().unwrap();
let version = xml_root.attr("version").unwrap();
println!("DDD XML structure version: {}", version);
let base_unit_svg = xml_root.get_child("SingleElementName", DEFAULT_NAME_SPACE).unwrap();
let baseunit_text = base_unit_svg.text();
let base_unit_in_svg: usize = baseunit_text.parse().unwrap();
println!("DDD SVG base unit: {}", base_unit_in_svg);
}- cargo add serde --features derive
- the 'derive' is for creating structs used for json data
- cargo add serde_json
use serde_json::Value;
use std::fs;
fn main() {
let file_path = "sample.json";
let data = fs::read_to_string(file_path).expect("Unable to read file");
// Parse the JSON string into the generic Value type
let root: Value = serde_json::from_str(&data).expect("Error parsing JSON");
// Example of accessing the meta and data fields
// the '.as_str().unwrap()' is to get rid of the quotes around the string, as you see around the date
println!("Metadata: Date - {}, Version - {}", root["meta"]["date"], root["meta"]["version"].as_str().unwrap());
for person in root["data"].as_array().unwrap() {
println!("{:?}", person);
}
println!("First persons name: {}", root["data"][0]["name"].as_str().unwrap());
}output:
Metadata: Date - "2024-07-15", Version - 0.1.0
Object {"age": Number(30), "city": String("New York"), "name": String("John Doe")}
Object {"age": Number(25), "city": String("Los Angeles"), "name": String("Jane Doe")}
First persons name: John Doe
json:
{
"meta": {
"date": "2024-07-15",
"version": "0.1.0"
},
"data": [
{
"name": "John Doe",
"age": 30,
"city": "New York"
},
{
"name": "Jane Doe",
"age": 25,
"city": "Los Angeles"
}
]
}use serde::{Deserialize, Serialize};
use std::fs;
// Define structs to match the JSON structure
#[derive(Serialize, Deserialize, Debug)]
struct Root {
meta: Meta,
data: Vec<Person>,
}
#[derive(Serialize, Deserialize, Debug)]
struct Meta {
date: String,
version: String,
}
#[derive(Serialize, Deserialize, Debug)]
struct Person {
name: String,
age: u8,
city: String,
}
fn main() {
let file_path = "sample.json";
let data = fs::read_to_string(file_path).expect("Unable to read file");
// Parse the JSON string into the generic Value type
let root: Root = serde_json::from_str(&data).expect("Error parsing JSON");
// Example of accessing the meta and data fields
println!("Metadata: Date - {}, Version - {}", root.meta.date, root.meta.version);
// Please note that the 'for person in &root.data' is a reference to the vector,
// so the original vector is not moved and you can use the vector again
for person in &root.data {
println!("{:?}", person);
}
let second_person = &root.data[1];
println!("Second persons name: {}", second_person.name);
}output:
Metadata: Date - 2024-07-15, Version - 0.1.0
Person { name: "John Doe", age: 30, city: "New York" }
Person { name: "Jane Doe", age: 25, city: "Los Angeles" }
First persons name: Jane Doe
json file: see above
- env::vars() (Bal17,p90)
- dotenv crate(Kol19,p59)
- clap crate(Kol19,p61)(The documented examples do not compile)
- std::env::args() (Bal17,p89).
For a code example, please see: commandline_parameters example
(Kol19,p68)
let addr = matches.value_of("address")
.map(|s| s.to_owned())
.or(env::var("ADDRESS").ok())
.and_then(|addr| addr.parse().ok())
.or(config.map(|config| config.address))
.or_else(|| Some(([127, 0, 0, 1], 8080).into()))
.unwrap();source: (Kol19,p54)
if log_enabled!(Debug) {
let data = get_data_which_requires_resources();
debug!("expensive data: {}", data);
}- RUST_LOG=trace cargo run
- (Kol19,p56)
- RUST_LOG=random_service=trace,warn cargo run
- This value of the RUST_LOG variable filters all records by the warn level and uses the trace level for targets starting with the random_service prefix(Kol19,p56).
- RUST_LOG_STYLE=auto cargo run
- (Kol19,p57)
- serde crate(Kol19,p72)
- Kol19,p362
- rustracing
- rustracing_jaeger
XXX
- sudo apt install wabt
- rustup target add wasm32-unknown-unknown
- cargo install wasm-bindgen-cli
- sudo apt install npm
See also:
- cargo generate --git https://github.com/rustwasm/wasm-pack-template
- cd YOUR_PROJECT
-
wasm-pack buildbuild the rust code generates .js and .html files -
npm init wasm-app wwwsee An npm init template for kick starting a project that uses NPM packages containing Rust-generated WebAssembly and bundles them with Webpack. - pushd www
-
npm install(install missing packages?) - modify files (which files, then index file etc?)
-
npm run start(This will automatically re-generate javascript web site(I think)) -
npm run build(release build; to 'dist' dir)
for npm run build see: How to make the Rust Game of Life WebAssembly work as a static website?
- wasm-pack test --chrome --headless
- wasm-pack test --node
see: Testing Conway's Game of Life
-
Source: Interfacing Rust and JavaScript
-
Minimizing copying into and out of the WebAssembly linear memory.
- Unnecessary copies impose unnecessary overhead.
-
Minimizing serializing and deserializing.
- Similar to copies, serializing and deserializing also imposes overhead, and often imposes copying as well.
- If we can pass opaque handles to a data structure, we can often reduce a lot of overhead.
- XXX what are 'opaque handles'
- instead of serializing it on one side, copying it into some known location in the WebAssembly linear memory, and deserializing on the other side.
- wasm_bindgen helps us define and work with opaque handles to JavaScript Objects or boxed Rust structures.
-
a good JavaScript-WebAssembly interface design is often one where large, long-lived data structures are implemented as Rust types that live in the WebAssembly linear memory, and are exposed to JavaScript as opaque handles.
- JavaScript calls exported WebAssembly functions that take these opaque handles,
- transform their data,
- perform heavy computations,
- query the data,
- and ultimately return a small, copy-able result.
- By only returning the small result of the computation, we avoid copying and/or serializing everything back and forth between the JavaScript garbage-collected heap and the WebAssembly linear memory.
- JavaScript calls exported WebAssembly functions that take these opaque handles,
See:
Code:
- Cargo.toml:
web-sys = { version = "0.3.60", features = ['console'] }in '[dependencies]' section - .rs:
console::log_1(&"Hello using web-sys".into());- at the top:
use web_sys::console;
- at the top:
-
cargo new --lib rustwasmhello(Hof19,p48) - Update Cargo.toml
[lib]
crate-type = ["cdylib"]
[dependencies]
mut_static = "5.0.0"
lazy_static = "1.0.2"lazy static to create a globally available instance of the GameEngine struct. This instance will then be used by all of the functions you’re exporting out of the WebAssembly module(Hof19,p60).
- python3 -m http.server 8000 --bind 127.0.0.1
- Open the browser to (http://127.0.0.1:8000/)
- Right click on the page and select 'Inspect(Q)'
- Click the 'Console' tab
- Decorating Rust code with
#[wasm_bindgen]triggers the invocation of a compile-time Rust macro(Hof19,p69).- Each time the compiler encounters this macro, it generates some code on your behalf(Hof19,p69).
- Some of it will be code that winds up in your .wasm module, but some of it will be metadata used to help generate the corresponding JavaScript output produced by the wasm-bindgen command-line tool(Hof19,p70).
The learning curve was too steep for me. I switched to egui.
- rustup toolchain install nightly
- rustup target add wasm32-unknown-unknown
- rustup default nightly
- cd ~/source
git clone https://github.com/leptos-rs/leptos.git- cd leptos/examples
- cargo install --force cargo-make
- cargo install --locked trunk
- cd ~/source/leptos/example
- cargo make start animated_show
- cargo make ci
- Components are the basic unit of composition and design in Leptos as well as most frameworks
- a componnet represent a section of the DOM, with self-contained, defined behavior.
Defining a component:
#[component]
fn App() -> impl IntoView {
// component body
...
}- #[component] annotates a function so it can be used as a component in your Leptos application.
- a componen is a function,
- that function can have zero or more arguments
- gathered together into a single props struct which is built by the view macro as needed.
- the function returns
impl IntoView- an opaque type that includes anything you could return from a Leptos view
- that function can have zero or more arguments
- the component body - run once, not a render function that reruns multiple times.
- signal, the basic unit of reactive change and state management in Leptos.
-
let (count, set_count) = create_signal(0);- seems like 'count' is the getter
- 'set_count' is the setter?
-
- Leptos defines user interfaces using a JSX-like format via the view macro.
- derived signals -
-
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev
-
cargo add egui
-
cargo add eframe
- cargo new PROJECT_NAME
- cd PROJECT_NAME
- cargo add ratatui --features all-widgets
- cargo add crossterm
- cargo add color-eyre
- eyre - error handling and reporting
- From ChatGPT
- Raw mode when building interactive terminal UIs that need to respond to every key press, mouse event, or terminal resize immediately.
- Raw mode disables line buffering, disables input echo, and gives the program direct control over input and output.
- This is essential for applications like text editors, dashboards, or anything that needs real-time interaction and custom key handling.
- Normal (cooked) mode when the application only needs to read user input line-by-line (e.g., waiting for the user to type a command and press Enter), or when running code that should behave like a regular command-line tool.
- In normal mode, the terminal handles line editing, input echo, and signals like Ctrl+C.
- Raw mode when building interactive terminal UIs that need to respond to every key press, mouse event, or terminal resize immediately.
- let terminal = ratatui::init();
fn main() -> color_eyre::Result<()> {
let mut terminal = ratatui::init();
loop {
}
ratatui::restore();
}- title
- title_bottom
- Text - a list of Lines.
- Line - a list of Spans.
- Span - a string with a specific style.
-
cargo add tui-textarea
- You can not combine TextArea with anything else in the same area. So you can't have 'name...: INPUT'
- you have to split it vertically and have 'name...:' in the left pane and the INPUT in the right pane.
See: Fix: 'cc' not found
sudo apt install build-essential
error: linker `cc` not found
|
= note: No such file or directory (os error 2)
error: could not compile `libc` due to previous error
warning: build failed, waiting for other jobs to finish...
error: could not compile `proc-macro2` due to previous errorMissing git: apk add git
/ # npm init wasm-app www
Need to install the following packages:
[email protected]
Ok to proceed? (y) y
events.js:377
throw er; // Unhandled 'error' event
...
npm ERR! code 1
npm ERR! path /
npm ERR! command failed
npm ERR! command sh -c create-wasm-app www
npm ERR! A complete log of this run can be found in:
npm ERR! /root/.npm/_logs/2022-11-18T08_30_45_545Z-debug-0.log
/ # create-wasm-app
sh: create-wasm-app: not found
/ # git
See: error
sudo apt-get install librust-atk-dev
see: fixed by chatgpt
sudo apt-get install libgtk-3-dev
hashmaps
See: official rust docker image
- cannot find crti.o
- RUN apk add --no-cache musl-dev
-
AMQP - Advanced Message Queuing Protocol
-
amiquip - seems to have gone stale.
-
Shk19: Mastering Distributed Tracing {Yuri Shkuro} 2019
-
aggregation - the formation of a number of things into a cluster.
-
Trace - follow execution events from a single operation accros functions and services.
-
- Measure - is identified by name, description and a unit of values.
-
Measurement - describes a single value to be collected for a Measure.
- TODO Is this just the actual value???
- The base class for all types of pre-aggregated metrics is called Metric. TODO what does this mean???
-
Span - represents an operation within a transaction see
- operation name - TODO how
- timestamps for start and finish.
- attributes - key-value pair list.
- Events - TODO what is this???
- Parent span identifier.
- Links to cusally-related spans via SpanContext. TODO what is this?
- SpanContext - Represents all the information that identifies Span in the Trace.
- information required to reference a Span.
- TraceId - worldwide unique
- SpanId - globally unique
- TraceFlags -
- Tracestate - TODO What is this???
-
Metrics -
- counter -
- gauge -
- TODO what do these mean:
- even model
- in-flight data model
- TimeSeries model
- All exporters consume data from Metrics Data Model via a Metric Producer interface defined in OpenTelemetry SDK.
- code dealing with Metrics should avoid validation and sanitization of the Metrics data.
- Instead, pass the data to the backend, rely on the backend to perform validation, and pass back any errors from the backend.
- TODO see Metrics data model
-
Baggage - simple mechanism for propagating name/value pairs.
- intended for indexing observability events in one service with attributes provided by a prior service in the same transaction.
-
Resources - captures information about the entity for which telemetry is recorded.
- For example, metrics exposed by a Kubernetes container can be linked to a resource that specifies the cluster, namespace, pod, and container name.
-
Context - an object that contains the information for the sending and receiving service (or execution unit) to correlate one signal with another.
- carries execution-scoped values across API boundaries and between logically associated execution units.
- MUST be immutable, and its write operations MUST result in the creation of a new Context containing the original values and the specified values updated.
-
Concern - a particular set of information that has an effect on the code of a computer program.
-
cross-cutting concern - a piece of software which is mixed into many other pieces of software in order to provide value.
- by their very nature, violate a core design principle – separation of concerns.
- traces and metrics.
-
propagator - serialize and deserialize cross-cutting concern values such as Spans (usually only the SpanContext portion) and Baggage
- TextMapPropagator - seems to be the only one right now(2023)
-
propagation - mechanism that moves context between services and processes. It serializes or deserializes the context object and provides the relevant information to be propagated from one service to another.
- Propagation is usually handled by instrumentation libraries and is transparent to the user, but in the event that you need to manually propagate context, you can use Propagation APIs.
-
Resources - captures information about the entity for which telemetry is recorded. For example, metrics exposed by a Kubernetes container can be linked to a resource that specifies the cluster, namespace, pod, and container name.
-
- TODO is this it? underlying Context mechanism for storing state and accessing data across the lifespan of a distributed transaction
-
Instrumentation Libraries - enables OpenTelemetry observability for another library
- See also Instrumentation Library
-
Collector - a set of components that can collect traces, metrics and eventually other telemetry data.
- TODO I think this is server that handles the data and sends them on.
-
OpenTelemetry client - TODO what is this?
- Is a client the crates used for getting opentelemetry into the user application? Versioning and stability for OpenTelemetry clients
- Common
- Logging
-
Log + opentelemetry-appender-log
- TODO it seems that Log is a wrapper and it also seems like Log contains 'trace' (what happened to separation of responsibility???)
- A logging facade provides a single logging API that abstracts over the actual logging implementation.
- TODO it seems that Log is a wrapper and it also seems like Log contains 'trace' (what happened to separation of responsibility???)
- TODO what does this mean? Logs Bridge API
- It looks to me like the tokio tracing includes both logging and tracing, but not otlp, so I wont spend time looking into tokio tracing at this time.
- The Tokio provided tracing was suggested for logging in the Project Status section of open-telemetry/opentelemetry-rust.
-
Log + opentelemetry-appender-log
- Metrics
- Tracing
- docker run --name jaeger --rm -d -p6831:6831/udp -p6832:6832/udp -p16686:16686 -p14268:14268 jaegertracing/all-in-one:latest
- docker run --rm -it --link jaeger -p8080-8083:8080-8083 jaegertracing/example-hotrod:1.6 all --jaeger-agent.host-port=jaeger:6831
- Access websites