Rust Features
- Borrow Checker
- No GC
- Lifetimes
- Mutable References
- Variables are Immutable by Default
Examples
fn main() { // functions are just codeblocks bound to names!
let mut x = 5;
x = 6;
let z = { // this is a codeblock
let z = 5;
println!("{z}");
5 + 6
};
println!("{z}")
}
fn add(x:i32) -> i32 {
x + 5
}Codeblocks
{ (statements;) * expression:t? } : tSo for example:
let x = {
let x = 5;
let y = 6;
x + y
};
{
println("bruh");
}; // type of unit, or formally ()
let x = {let x = 6; let y = 7; println!("{}", x+y); };
// reduces to
{} : ()
fn print_thing(x:i32) -> () {
println!("{}", x);
}fn main() {
println!{"Hello"};
let x = 5;
let y = 7;
x + 8;
}If Expressions (not Statements!)
// WRONG!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
fn main() {
if (true) {
4
} else {
5
} // expected (), found integer
}
// RIGHT
fn main() {
{if (true) {
4
} else {
5
}};
}
// ALSO RIGHT
fn main() {
let z = {
if (true) {
4
} else {
5
}
};
}Ownership
Rules of Ownership
- Every value has an owner
- Every value has only one owner at a time
- When the owner goes out of scope, the value is dropped.
This removes the issue of the double-free. This is a GUARANTEE that there will ever only be one free for a malloc.
Ownership means that you can only use the current owner to refer to the value. For example:
fn main() {
let x = 5;
let y = String::from("hello");
let z = y;
println!("{y}"); // doesn't work. Borrow of moved value error!
println!("{z}"); // fine and dandy
}But cloning allows you to use both (deep copy):
fn main() {
let x = 5;
let y = String::from("hello");
let z = y.clone();
println!("{y}"); // fine and dandy
println!("{z}"); // fine and dandy
}Ownership solves the issue of not being able to pass ownership outside of the function - like in C if you returned a pointer to a value, but the value gets dropped after the function terminates, it doesn’t work. But Rust doesn’t have this problem, since you can just pass ownership of the value.
fn main() {
let y = String::from("hello");
takes_ownership(y);
println!("{y}"); // Borrow after move error!
}
fn takes_ownership(a:String) {
println!("{a}")
} // here when function returns, "hello" is dropped, as a goes out of scopeBut consider
fn main() {
let x = 5;
let u = x;
println!("{},{}", x, u); // this works because 5 is a primitive on the stack, not on the heap - though technically these rules apply to both the stack and the heap
// but for the heap, it matters more:
let y = String::from("hello");
takes_ownership(y);
let z = gives_ownership();
println!("{z}"); // works
let c = gives_and_takes(z); // takes back ownership
println("{c}");
borrows_data(&c); // doesn't transfer ownership, only borrows
println("{c}"); // so this works fine and dandy
}
fn takes_ownership(a:String) /* takes ownership */ {
println!("{a}")
}
fn gives_ownership() -> String {
let b = String::from("world");
b // gives up ownership of b
}
fn gives_and_takes(h:String) /* takes ownership */ -> String {
println!("{h}");
h // gives back ownership
}
fn borrows_data(g:&String) {
println!("{g}");
}