7.4 KiB
up:: Rust tags::
Rust Notes
Rust is a modern systems programming language with:
- memory safety (no garbage collection)
- no null values
- no exceptions
- modern package manager and build system (similar to npm)
- no data races (race conditions)
Installing rust
on macos/linux
after running command follow onscreen instructions
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
to confirm installation success run the following:
-> rustc --version
rustc 1.67.1 (d5a82bbd2 2023-02-07)
-> cargo --version
cargo 1.67.1 (8ecd4f20a 2023-01-10)
-> rustup --version
rustup 1.25.2 (17db695f1 2023-02-01)
in VSCode
install rust-analyzer extension
Exploring Cargo
By running cargo new <name>
you will create a new rust project containing a git repository
Calling cargo new --help
will bring up help view
the Cargo.toml
file is always at the root of a rust project. Its essentially the package.json
in npm
The dependency section contains whats called Crates
. The central registry for all crates is found at Crates.io.
after a crate is installed, youll see the name of the crate, the version, and a thumb meaning its isntalled
[dependencies]
package = 1.0.0 👍
Building project
To build the cargo project you run cargo build
. This will build all crates and your rust code.
Running project
To run the cargo project you run cargo run
which will execute the binary file that was previously built
System wide crates
We can also use cargo to install rust binaries that can be used system wide. A tool called cargo-expand
can be installed by running cargo install cargo-expand
in the terminal
this takes a minute
cargo-expand is using the nightly build of cargo/rust so you cannot call it in the terminal yet
lets check what we have installed by running rustup toolchain list
. Looks like we're on the stable version
lets install the nightly toolchain using the following command
rustup toolchain install nightly-aarch64-apple-darwin
Now if we run rustup toolchain list
, we'll see the nightly and the stable with stable being default - we can now call cargo expand
in the terminal
The stack
Rust calculates the size of a stack frame at compile time This means, if a function contains a variable, the stack frame will have just enough space for that variable. If a new frame is added with a parameter and a variable, the stack frame will have just enough space for the two variable, slightly larger than the previous stack frame
The stack has a limited size, determined by the machine architecture, and will result in a stack overflow error if you overflow the stack with something like infinite recursion
The Heap
What is the heap?
- its a region fo the process emmory that is NOT auto managed
- it has no size restrictions
- its accessible by any function, anywhere in the program
- we should avoid heap allocations as they are expensive
When you allocate a value on the heap, you store a pointer to the address of the allocated data
POINTER e = ALLOCATE INTEGER 7
// e = 0xf578bb60
memory is manually allocated and deallocate on the heap. If you dont deallocate the memory, you will have a memory leak, which wont be released until the program exits
Smart Pointers
smart pointers essnetially just deallocate the heap memory automatically
let e = Box::new(7);
Memory layout of binary
who cares
Basic data types
u8 - unsigned 8 bit (can only be positive) i8 - signed 8 bit (can be negative) u/i + size (8,16,32,128)
bool - 0/1 usize, isize - positive/negative size
char - single character (always 4 bytes)
Functions
the main()
function is the entry point to any rust application
function names are written in snake case i.e. some_function_name()
annotate all parameters
annotate return type
fn some_func(arg: f32) -> f32 {
// return f32 type
// if you dont use return keyword and without semicolon, value will be implicitly returned
}
We may see functions with a BANG attached to it, like so someFunc!()
. That BANG indicates that this function is a MACRO.. A macro essentially is a shortcut to template code. So when we call the macro function, it will expand the code on build to something like so:
someFunc!('some arg');
// expands to
bunc::of::junk -> stuff -> no idea what im writing{
match(junk){
no idea -> 'some arg'
}
}
// this is just an example, this is nothing like the actual syntax of rust
Variables
we use the let
keyword to define variables
let newVar = 5;
There's no need to define variable types as the rust compiler can usually infer the type
All variables in rust are immutable by default. If we try to update the variable, the compiler will cry about it.
let a = 'b';
a = 'c' // compiler error: variable immutable
let mut a = 'b';
a = 'c';
// a == 'c'
Standard Library
STD Module I/O (std::io)
// need to allocate empty mutable string first
let mut value = String::new(); // value lives on heap since size is not known at compile time - String is type of smart pointer
io::stdin().read_line(&mut value);
Ownership
there are a few rules that variables follow:
- each value in rust is owned by a variable
- when the owner goes out of scope, the value will be deallocated
let mut input = String::new()
where input is owner of String - there can only be ONE owner at a time
(
let mut input = String::new()
where input is owner of heap allocated String)
when passing owner to function, the function parameter now becomes the owner, and when that function is finished executing, you can no longer use the initial variable
let mut input = String::new(); // input is now owner
some_fn(input); // some_fn parameter is now owner
io::stdin().read_line(&mut input); // throws error as input is no longer available
if you dont like this, see Borrowing
Borrowing
by passing references, we can "borrow" a variable
fn some_fn(s: &string){} // s is not the owner of the string due to &
let val = String::new();
some_fn(&val) // reference is immutable
let mut val = String::new()
some_fn(&mut val) // reference is mutable
WITHIN a scope, you can have only one of:
- as many immutable references as you want
- one mutable reference
// cannot borrow 'input' as immutable because its already borrowed as mutable
Result Type
When a method returns a type Result
, it returns essentially a javascript promise with a success (then) and an error (catch).
you can call .unwrap()
on the result
if result was a success, the unwrap will yield the contents of the success
if result was an error, it will terminate the application
Parse string to number
Sometimes when getting input from the user for a number, you want to actually do some operation on that number.
io::stdin().read_line(&mut value).unwrap()
say we want to convert this value
to an f32
- you would first need to ensure the string is trimmed
let new_value = value.trim();
then you can call parse on the string
new_value.parse::<f32>().unwrap();
the <f32>
part is so parse
an ensure what type we're wanting to convert it into
Resources
For more details on rust syntax, go to this file Rust Syntax overview
End
Now lets head on over to creating a http server Rust http server