Overview
What is Rust
- A statically complied language use LLVM as backend.
- supports many platform and architectures.
- used for a wide of range devices.
Why Rust
- Compile time memory safety.
- No uninitialized variables
- Mostly no memory leaks.
- No use-after-free and double frees.
- No
NULL
pointer. - No forgotten locked mutex.
- No data races between threads.
- NO iterator invalidation.
- No undefined behavior at runtime.
- Array access is bounds checked.
- Integer overflow is defined.
- Modern Features
- Enums and pattern searching
- Generics
- No overhead FFI(Foreign Language Interface)
- Useful dev tools
- Great compile errors.
- Built-in dependency manager.
- Built-in support for testing.
Common Programming Concepts
Variables and Mutability
Variables are immutable by default, make them mutable by adding mut
in front of the variable name.
fn main() {
let mut x = 5;
x = 6;
println!("x: {x}");
}
Constants
Constants are immutable, but there are a few differences between constants and immutable variables.
mut
is not allowed to add in front of constants, constants are immutable always.- declare constants with
const
instead oflet
. - constants must be annotated.
- can be declared in any scope, including the global scope.
- constants must set only to a constant expression, not a value computed in runtime.
// naming convention: all uppercase with underscores between two words.
const TWO_HOURS_IN_SECONDS: u32 = 2 * 60 * 60;
Shadowing
You can declare a new variable with the same name as a previous variable, the second variable overshadows the first.
fn main() {
let y = 1:
let y = y + 1;
{
let y = y * 2;
println!("inner scope y: {}");
}
println!("y: {y}");
}
differences between shadowing and making a variable as mut
:
- get a compile-time error when resigning the variable without
let
. - shadowing can change variable type.
let space = " ";
let space = space.len();
Data Types
Rust is statically typed language, every value in rust is of a certain data type, there are two data type subsets: scalar and compound.
Scalar Types
A scalar type represents a single value. Rust has four primary scalar types:
- integers
i8/16/32/64
u8/16/32/64
isize/usize
i32
is the default type- use
_
as a visual separator,10_222
- allow a type suffix,
42u8
- floating-point numbers
f32/64
f64
is the default type
- booleans
true/false
- 1 byte in size
- characters
char
- specify
char
literals with''
- specify
string
literals with""
- 4 byte in size
- represent a Unicode Scalar Value
Compound Types
- tuple
(1, 2.0, 3, 'a')
- array
[1, 2, 3]
let a: [i32; 5] = [1, 2, 3, 4, 5] let a = [3; 5]
- invalid array element access raise an
runtime
error
Functions
fn main() { // program entry
println!("Hello, world!");
another_function(5);
let x - five();
println!("The value of x is: {x}")
}
fn another_function(x: i32) {
println!("The value of x is: {x}");
}
fn five() -> i32 {
5
}
main
is the entry point of programfn
define new functionssnake case
as the conventional style for function and variable namesparameters
are special variables that are part of a function’s signature,arguments
are concrete values for these parameters- type of each parameter is required in function signature.
- function bodies are made up of a series of
statements
optionally ending in anexpression
. - statements are instructions that perform some action and do not return a value, so you can’t assign a
let
statement to another variable. - expressions evaluate to a resultant value, calling a function/macro, a new scope block created with curly brackets are all expressions.
- in a new scope block, if you add a semicolon to the end of an expression, you turn it into a statement.
- expressions can be part of statements.
Control Flow
- if
- condition must be a
bool
- use
match
if too many cases - if is an expression, so it can be on the right side of a
let
statement.
- condition must be a
- loop
- you can return values from loops
- loop labels to disambiguate between multiple loops, which begins with a single quote
- while
- used for conditional loop
- for
- used for looping through collections
fn main() { let a = [1, 2, 3] for v in a { println!("the value is: {v}") } }
Range
like(1..4)
Ownership System
Ownership is a set of rules that govern how a Rust program manages memory. programs have to manage the way they use a computer’s memory while running.
- garbage collection
- programmer explicitly allocate and free the memory
- Rust: memory is managed through a system of ownership with a set of rules that the compiler checks
Stack and Heap
Both the stack and the heap are parts of memory available to your code to use at runtime. The stack stores values in the order it gets them and removes the values in the opposite order, which is referred to as last in, first out. Two ways of manipulating data in stack:
- Pushing onto the stack
- Popping off the stack All data stored on the stack must have a known, fixed size. The heap is less organized: when you put data on the heap, you request a certain amount of space. The memory allocator finds an empty spot in the heap marks it as being in use, and returns a pointer, which is the address of that location. The process is called allocating on the heap or allocating. The pointer is a known, fixed size, which can be stored on the stack.
Pushing onto the stack is faster than allocating on the heap because the allocator never has to search for a place to store new data; the location is always at the stack.
Allocating space on the heap requires more work because the allocator must find a big enough space and then perform bookkeeping to prepare for the next allocation.
When calling a function, the values passed into the function(including, pointers) and the function’s local variables get pushed onto the stack, and get popped off the stack when over.
Ownership Rules
- Each value has an owner.
- There can be only be one owner at a time.
- When the owner goes out of scope , the value will be dropped.