My recall of what are essentials in Rust. It is a learning documentation. It is "recall and spaced repeation" approach.
Why
In C#, a language I have been coding for years, and of course, love it, a common error is NullReferenceException
. It means your code holds a reference that points to "nothing", there is nothing in that location in the memory. In other word, it is invalid.
Memory is a limited resource and many programs try to get a share. Memory magement is crucial to any programming language. Unused memory must be returned to the operating system. In C#, there is Garbage Collector (GC). It has some overhead on your program. I am far from knowning the detail here.
So far, as I know, Rust was designed with those problems in mind. The language makes it impossible for developers to make those mistakes.
Heap and Stack
They are two structures of memory to store data. Stack is designed to store fixed-size values such as numerics, literals. Heap is designed to store unknown size data, known as Reference type.
When your code declares a reference type, what the code holds is a pointer stored on the Stack. The pointer knows where to look for the value on the Heap. And NullRefrenceException
is when the point is on the Stack, but the location on the Heap has gone.
Heap and Stack are essential concepts for developers to understand programming languages.
Mutate or Immutable by default
Another common, nasty problem is the "race condition" – many paths (code) modify to the same location (data). Imagine that you have some money in your pocket. Someone else "access" your pocket and get some of it without your knowing. Surprise! It is a host of unexpected errors especially in the production.
Rust is designed with "immutable" by default. This code is valid in many programming languages except Rust
let name: String = String::from("Thai Anh Duc");
name.push_str("Oh No!");
Rust requires your "awareness". If you intend to modify something, say it explicitly with the mut
keyword.
let mut name: String = String::from("Thai Anh Duc");
name.push_str("You Rock!");
Ownership
This concept was new to me. I have not thought of ownership at the programming language level. The "Ownership" concept appears to me when I do the domain design, data model.
A value always has one and only one owner in a scope. A scope is denoted by
. Once the scope ends, everything inside the scope is dropped unless ownership is moved.
Ok, so who is the owner? and what does it own? Let’s look at this code
let name: String = String::from("Thai Anh Duc");
There is a variable name
which is a String that has a literal value "Thai Anh Duc". In short, there are "variable" and "value".
"Variable" is the owner.
"Value" is owned by the variable (the owner).
let name: String = String::from("Thai Anh Duc");
// This works fine because name is the owner
println!("My name is {name}");
// owner is moved to
let it_is_mine: String = name;
// This code will not compile because what name owns was moved to it_is_mine.
// In other word, name points to nothing
println!("My name is {name}");
// This is ok
println!("My name is {it_is_mine}");
The ownership is applied for reference types. Value types are different. The value is copied. Each variable owns its own data. The below code works fine
// This is a literal of string, it is fixed-size. The value is stored in the stack
let name = "Thai Anh Duc";
// This works fine because name is the owner
println!("My name is {name}");
// Another copy of data is created and copied_name owns the new data
let copied_name = name;
// Both work fine
println!("My name is {name}");
println!("My name is {copied_name}");
Move
The transfer of ownership is a Move. The actual data is unchanged (the data on the heap).
Copy
Another copy of data on the stack is created for the new variable. Both variables operate on their own data. It is safe.
Drop
When a scope ends, Rust performs a "Drop" to release memory allocated in the scope. Notice that a reference type is clean up unless its ownship is moved to the consumer. It is done by returning a value.
fn main(){
let name: String = String::from("Thai Anh Duc");
let hi = move_ownership(name);
println!("{hi}");
}
fn move_ownership(name: String) -> String{
let say_hi : String = String::from("Hi: ") + &name;
say_hi
}
Reference
In the previous example, the &name
is used to access the location of the variable. It is the pointer to a location on the heap
Bottom lines
I recalled and documented what I have learned from Rust Understanding Ownership.