Rust
Table of Contents
1. Syntax
1.1. Type
There are two ways to construct new types:
enumfor sum typestructfor product type
Types only own the data.
The methods are composited by implementing trait.
1.1.1. enum
An enum type takes up the memory space as large as its largest variant.
enum <ident> {
<ident>(<data types>),
<ident>,
}
The memory layout of enum contains:
- tag: small integer for variant identification
- payload: the actual data.
1.1.2. struct
A struct type holds sequence of data, just like C.
1.1.3. trait
A trait definition declares the method signatures,
trait <ident> {
fn <ident>(<parameters>) -> <return type>,
}
Actual implementation is needed for each type.
impl <trait ident> for <type ident> {
fn <ident>(<parameters>) -> <return type> {
<implementation>
},
}
A function can be defined on a generic type that implements certain traits
fn <ident>\<T: <traits+>\>(<ident>: T) { ... }
This code does static dispatching, which generates the function for each concrete type on demand, just like C++ template.
Dynamic dispatching is also possible through dyn keyword:
fn <ident>(<ident>: Box\<dyn <trait>\>) { ... }
which calls the function via the function pointer in the vtable generated automatically, similar to C++ virtual method.
Box, & or other indirection is needed in order to fix the size of argument for compilation.
1.1.4. Standard Types
1.1.4.1. Result<T, E>
result can be chained in a monadic way with ? suffix operator.
Result provides a collect method that maps Iterator<Result<T, E>> -> Result<Iterator<T>, E>.
E should implement Err trait for fully featured debugging experience.
1.1.4.2. Optional<T>
1.2. Indirection
References (&) necessitate the existence of lifetime
for the borrow checker to enforce the memory safety.
1.2.1. Ownership and Borrowing
Only single variable can own a data. Others should use references for the data.
Multiple references (&) can exists simultaneously, while there can only be single mutable reference (&mut).
1.2.2. Smart Pointers
Smart pointers are pointer with additional functionality. Some of the smart pointers need extra information stored with the memory address, in which case the pointer is called a fat pointer.
Box<T>moves the ownership of the pointer just like C++std::unique_ptrdoes.Rc<T>stores reference counts with the pointer.Arc<T>enables sharing pointer across threads.Tis required to implementSyncfor the ability to be shared across multiple threads.
1.2.3. Lifetime
Lifetime is a range in the source code in which the reference is valid. Lifetime ends when the scope is terminated. Note that empty lines and lines before the assignment does not count.
A generic lifetime for multiple references represents the intersections of all their lifetimes.
fn f<'a>(x: &'a T, y: &'a U) -> V: 'a { ... };
The return value is valid while the all the arguments are valid.
A lifetime constraints can be used for complex case:
fn f<'a, 'b, 'c>(x: &'a T, y: &'b U) -> V: 'c
where
'a: 'c,
'b: 'c,
{ ... }
This means both lifetime 'a and 'b lives longer than 'c.
The default lifetime is indicated by '_,
and the lifetime valid throughout the code is indicated by 'static.
'static is used for string literal, and by default Future type.
1.3. Macro
Macro argument delimiter can be any of (), {}, [].
The delimiter choice is purely conventional.
Best practice is to use:
()for function call like macro{}for code block like macro[]for array like macro.
1.3.1. Declarative Macro
Defined with !macro_rules macro with the following syntax:
macro_rules! <macro ident> {
(<$meta variable>:<fragment specifier>) => {
<replacement code($meta variable)>
};
// more matches...
}
The fragment specifier can be
expridenttypathliteralblockstmtpat
1.3.2. Procedural Macro
Compile-time function on the token sequence to token sequence.
It is usually as parser-retokenization pair with syn and quote crate respectively.
Three form of procedural macro exists:
- function-like: called inline just like function
- derive macro: annotated on top of a code block or statement as
#[derive(...)] - attribute macro: annotated similarly but with more flexibility.
1.4. Async
Rust provides Future type that represents the state machine, but does not
runs it. Separate async runtime is required to enable task creation from Future.
A function with async keyword is simply a function that returns Future,
with the state machine containing states for each .await statement within the code block.
tokio is the widely used and feature-rich async runtime framework.
2. Toolchain
The rust toolchain can be easily managed by rustup.
2.1. cargo
The RUST crate manager and packager.
2.2. rustc
--emit=miroutput Mid-level IR--emit=llvm-iroutput LLVM intermediate representation--emit=llvm-bcoutput LLVM Bitcode
3. References
- Rust Programming Language
- The Rust Programming Language - The Rust Programming Language
- A Simpler Way to See Results - YouTube
- but what is 'a lifetime? - YouTube
- Rust’s most complicated features explained - YouTube
- Comprehending Proc Macros- YouTube
- 75: I love declarative macros in Rust- YouTube
- Rust's Witchcraft - YouTube