- Constant items can be completly computed at compile time, and any code that refers to them is replaced with the constant’s computed value during compilation.
- All values, no matter their type, must start at a byte boundary. WE say that all values must be at least byte-aligned.
- In the CPU and the memory system, memory is often accessed in blocks larger than a single byte. For example, in a 64-bit CPU, most values are accessed in chunks of 8 bytes (64 bits) with each operation starting at an 8-byte-aligned address. This is referred to as the CPU’s word size.
- Operations on data that is not aligned are referred to as misaligned accesses and can lead to poor performance and bad concurrency problems. For this reason, many CPU operations require, or strongly prefer, that their arguments are naturally aligned. A naturally aligned value is one hose alignment matches its size. So, for example, for an 8-byte load, the provided address would need to be 8-byte-aligned.
- Build-in values are usually aligned to their size, a u8 is a byte aligned, a u16 is 2-byte-aligned, a u32 is 4 byte aligned and u64 is 8 byte-aligned. Complex types - types that contain other types - are typically assigned the largest alignment of any type they contain. For example, a type that contains a u8 and a u32 will be 4-byte aligned because of the u32.
- For example slices are two-word pointers, one for the location of the data in memory and the second describes its size.
- You can only implement a trait for a type that is declared on your crate or a trait that is declared on your crate.
- Auto-traits are added by the compiler automatically (such as Sync and Send). If a type becomes !Sync it will also be automatically set. So if you are building a library you may have breaking changes for your users without you noticing.
- If the trait is object-safe, users can treat different types that implement your trait as a single common type using dyn Trait.
- To be object-safe, none of a trait’s methods can be generic or use the Self type. Furthermore, the trait cannot have any static methods.
- Debug, Clone, Default, PartialEq, PartialOrd, Hash, Eq and Ord.
- As a feature: Serde::{Serialize, Deserialize}
- Errors should implement: std::error::Error
- Deref allows some T to call methods on some type U by calling them directly on the T-typed. Example Box allow us to call methods of MyType directly.
- AsRef which allows users to easily use &WrapperType as an &InnerType.
- Borrow is tailored for a much narrower use case: allowing the caller to supply any one of the multiple essentially identical variants of the same type. It coulde, perhaps have been called Equivalent instead. For example, for a HashSet Borrow allows the caller to supply either a &str or a &String. While the same could have been archived with AsRef, that would not be safe without Borrowś additional requirement that the target type implements Hash, Eq and Ord exactly the same way.
- If the code you write needs ownershpit of the data, it should generall also make the caller provide owned data, rather than taking values by reference and cloning them.
- See page 46 on how to create an explicit desctructors (Since trait Drop eat any error and some users may want to be more careful).
- We can use the Type system to guide users, if some methods are not suppose to be available we could use an zero sized type + generic types for that:
struct Grounded;
struct Launched;
struct Rocket<Stage = Grounded> {
sage: std::marker::PhantomData<Stage>
}
impl Default for Rocket<Grounded> {}
impl Rocket<Grounded> {
pub fn launch(self)-> Rocket<Launched> {}
}
impl Rocket<Launched> {
pub fn accelerate(&mut self) { }
}
impl<Stage> for Rocket<Stage> {
pub fn color(&self) -> Color {}
}
- Fuzzers keep trying your code with random input until it panic. A good library for this is cargo-fuzz. Example:
libfuzzer_sys::fuz_target!(|data: &[u8]| {
if let Ok(s) = std::str::from_utf8(data) {
let _ = url::Url::parse(s);
}
})
- The fuzzer will generate semi-random inputs to the clorsure. NOtice that the code here doesn check wheter the parsing succceeds or fails.
Insterad itś looking for cases where the parser panics or otherwise crashes due to internal invariants that are violeted.
- A good library for it is proptest.
- For times where you want to check not only that your program doesn crash byt also it does what it’s expected to do.
- It’s the same idea of the Fuzzer, but normally you also give the input to a naive algorithm that you know is correct. And compare the result
with your production code, if they diverge you have a bug.
- Declarative macros are those defined using the macro_rules! syntax. They are useful when you find yourself writing the same code over and over.
- You define how to generate code given some input tokens rather than just writing what code gets generated.
- There are three types of Procedural Macros: (1) function like macros (e.g. println!); (2) Attribute macros, like #[test] and (3) derive macros, like #[derive(Serialize)].
- In Rust, an async interface is a method that returns a
Poll
.
enum Poll<T> {
Ready(T),
Pending,
}
- It is sometimes useful to have objects that are guaranteed not to move, in the sense that their placement in memory does not change, and can thus be relied upon. A prime example of such a scenario would be building self-referential structs, as moving an object with pointers to itself will invalidate them, which could cause undefined behavior.
doc
- A Waker is a handle for waking up a task by notifying its executor that it is ready to be run. doc
- In general, you should be very careful with executing compute-intensive operations or calling functions that could block in an asynchronous context. Such operations should either be converted to async where possible or executed on dedicated threads.
- A rule of thumb is: no future should be able to run for more than 1 ms without returning Poll::Pending
- Invariants: something that must be true for your program to be correct
unsafe fn
signals to users that they must hold the invariants (and be careful) while invoking the function
unsafe {}
allows users to perform unsafe operations
unsafe fn
allow users to perform unsafe operations in the whole function, but this was a mistake and it will change.
- Unsafe Traits are not unsafe to use, but unsafe to implement.