Understand the difference between aliasing and mutability.
Aliasing refers to having several immutable references to T. Mutability refers to having single mutable reference to T. Rust follows Aliasing XOR Mutability rule. You’re either allowed to have multiple references to an object or have single unique mutable reference.
coercion: coercion refers to changing types on run time to a related type. for example: converting from i16 to u16. but not from sttring to int.
Question: how rust defines the relation about conversion from one type to another?
unwrap() : unwrap Result<T, E> into T.
expect() : catches an error and panic with that
is_ok() : returns bool whether returned val is Result Ok.
parse() : parses number to string
? : when returning Result<T, E> type from a func, if want to return error as soon as you encounter it, use ? .
::from() : convert primitive type to custom type
.into() : converts custom to primitive type, reverse of From
Every type whether primitive or custom have traits implemented. Each trait have different functions that the type uses. Example: println! utilises Display trait for types like i32 or String.
Thus, these types need to implement the following traits in order to use println! like functions.
Debug trait implements {:?} functionality. So, whenever you implement a function for a generic type,
usize : used as unsigned size type, basically ui32
const fn: const functions are evaluated at compile time, and have some limitations like for can’t be used.
The interpretation happens in the environment of the compilation target and not the host.
Deref trait is used to enforce any smart pointer to have same functionality as a normal pointer.
If a type implements Deref, it means it can handle shared references to inner type.
If a type implements DerefMut, it means it has access to mutable reference (exclusive) reference to inner type.
Pointers and References
Functions and traits
fn type refers to any function type which also include closures.
there is no such thing as &fn(type) -> return_type as fn() is already a pointer type. Each named function has a distinct type since Rust PR #19891 was merged.
there is a difference between function item type and function return type, any named function when referred in an object always return a function item type: fn(arg)->return_type {name} which is different than function pointer: fn(arg)->return_type.
Use of traits is advised as it compiled to direct function call which is better than function pointer. How?
This means that when you call func_of_func with a function item such as func, callback will be compiled to a direct function call instead of a function pointer, which is easier for the compiler to optimize.
Function pointers have to be dereferenced to be called while a function call is directly called from stack.
When passing function items as args in function calls, it can’t accept closures as closures doesn’t implement FnMut trait.
fn item types vs fn pointer type
item type: function types that are assignable at compile time.
pointer type: assignable at run time
A big question mark in my mind: how does any language define and knows what is compile time abstractable and what is run-time?
When you refer to a function by its name, the type you get is not a function pointer (e.g. fn(u32) -> bool). Instead, you get an a zero-sized value of the function’s item type (e.g. fn(u32) -> bool {foo}).
A big unlock for understanding why closures closely emulates functions are how they are treated by the language. In rust, closures are objects that can implement Fn* traits which allows them to be callable. Rust desugars closure || a + 1 with a trait definition like this:
Trait objects and Fat pointers
rustc want everything to be done at compile time. It forces you to write things in such a way that less and less things are left to be done dynamically or at runtime.
Rust accomplishes this by performing monomorphization of the code using generics at compile time. Monomorphization is the process of turning generic code into specific code by filling in the concrete types that are used when compiled. In this process, the compiler does the opposite of the steps we used to create the generic function in Listing 10-5: the compiler looks at all the places where generic code is called and generates code for the concrete types the generic code is called with.
That’s why rust’s way of handling generics is quite simple and straightforward. Let’s say you take a function F and supplies it’s arguments with generic parameters <T>. Now, since rust doesn’t know it’s concrete types but it does know what other type implement this trait, and it just copies the function with all the concrete types so that at runtime, it just becomes a function call and no additional overhead is implied due to generics.
Now, you can do other thing as well, and that is, impl trait objects in return type of the function. In usual generic argument function fn f<T: Bar>(…)->T, caller determines the concrete type of the function, but in case of impl Trait fn f(…) -> impl Bar, callee determine what function type to return so the caller only has an object of that trait type and doesn’t know what concrete type it belongs to. But note that, impl trait only works when all return type is of one concrete type. So, in the example show below, function rands(x: bool) -> impl Bar doesn’t work because it returns two different types, Foo and Baz. So, rustc throws an error that first match expression returns a Foo and expects second to be Foo as well.
Below example doesn’t work, because of how trait objects are implemented in Rust. Every trait objects is associated with a vtable. That’s how fat pointers work in rust, they generally have extra data as compared to normal pointers. In case of trait objects, it contains a vptr to trait’s vtable. So, when the function is called, vptr references the vtable with an offset which calls the required function implemented by that concrete type.
Adding a &self to trait function makes this work, because now rust has a concrete object whose function will be determined using vtable. Rust creates a vtable containing function pointers to each implementor class of the trait. So, when you call rands(), it returns a concrete type that implements Bar, and then when you call .bar(),
rust can look up at the vtable to determine which trait objects function call happens. Following diagram explains how rust determines which function to call depending on vtable. Pros of this: Rust only calls what is needed, so even if a type implements ton of traits, when you use a trait object, it only creates a vtable for the types implementing that trait object and not for all other traits implemented by the type.
Lifetimes are used in association with references to tell compiler how much you need a particular reference and should compiler keep it alive for that lifetime.
Very nice implementation of StrSplit by Jon Gjengset shows nice usage lifetimes, multiple lifetimes and generics.
Generics
Can define generic trait, then impl can be generic or concrete, and it can be further restricted with other traits.
Macros
Some examples to learn rust declarative macros from: