Rust: Basic Rust Concepts

Rust: Basic Rust Concepts

Using analogies to explain Rust concepts

Rust Lang as a lot of people will say was created to replace C++ but notwithstanding, Rust is an awesome language to learn especially if you are looking at becoming a systems engineer, game developer or if you pretty much want to do low-level stuff. To learn more about rust, you can check the official Rust website.

In a short answer, Rust is a typed language that focuses mainly on memory safety and performance. Some of her features are Zero-cost abstraction, Borrowing, ownership, and many more. This article was written to explain a few of these concepts for newbies in Rust.

Zero-Cost Abstraction: Memory management is something a lot of engineers do not know about mostly because they have a garbage collector that helps them with memory management Rust, on the other hand, does not have a garbage collector instead it uses a zero-cost abstraction method. Zero-cost abstraction is what you don't use, you don't pay for, whether you use the abstraction or you went for "manual" implementation you end up having the same costs =>same speed, same memory consumption.

Ownership: Ownership is a very important aspect of rust that we need to understand, so basically, ownership is how your program manages memory, in Rust's case memory is stored in two places, the stack, and the heap. A stack is where rust stores data with a known fixed size, an example of the data rust stores on a stack is an integer because an integer knows its size at compile-time. Its value; and the variable its assignee to will be stored on the stack. In a case where we have a vector(or any data type or structure that does not have a fixed size) that can change size at runtime, the value will be stored on the stack and the heap; on the stack, we store a representation of the vector, then a pointer to a memory address on the heap, the value of the vector will be stored on the heap too. In rust, ownership was created to solve the dangling pointer problem. The dangling pointer problem is where a pointer becomes invalid because the value was moved to another place and dropped.

Ownership has three rules: The first rule, each value in rust has a variable that identifies as its owner, The second rule is that there can be only one owner at a time, this means if you reassign a value to a new variable, it invalidates the first variable and makes the new variable its owner, the third rule of ownership when the owner is out of scope, the value is dropped (A scope is a range of which a variable is valid)

fn main() {
  let theOwner = String::from("value");
  displayValue(theOwner);

  println! ("the owner {} will throw an error because its value has been moved", theOwner);
}


fn displayValue(newOwner: String) {
  println!("This new owner will take ownership of the value and make the old owner invalid {}", newOwner );
}

In the code above we can see that a string was passed to a variable called theOwner here, we can say that variable theOwner takes ownership of the sting called "value". we then defined a function called displayValue and this takes an argument which is theOwner, remember our ownership rules say there can be one owner at a time and when a value is reassigned, the new variable takes ownership. In the code, variable newOwner takes ownership of string "value" hereby making variable theOwener invalid. This means the last part of the code inside the fn main function will throw an error. The reason Rust does this is to prevent memory safety bugs in our code. This leaves us with a question, how can we ensure we use the same value without taking it ownership? Rust created a clone method: A clone method allows us to copy a variable without moving it.

fn main() {
  let theOwner = String::from("value");
  displayValue(theOwner.clone());

  println! ("the owner {} will not throw an error because its value has just been copied so this works", theOwner);
}


fn displayValue(newOwner: String) {
  println!("This value is cloned or copied  {}", newOwner );
}

Now we just implemented a clone method on theOwner variable; what this does is, create another string "value " on the heap (this is because a string in Rust doesn't have a fixed size hence, Strings are stored on the heap) and point it to variable newOwner. The result of this is that both variable theOwner and variable newOwner each own different string "value" which is on the heap and can use them independently without errors.

copy method rust.png

The problem with this is that having too many copies on a heap can cause an overhead but Rust have a fix for this and it's called Borrowing.

Borrowing: Borrowing a variable creates a reference to the value of the variable, it doesn't create a copy of the value nor move the value of the variable.

fn main() {
  let theOwner = String::from("value");
  displayValue(&theOwner);

  println! ("the owner {} will  not throw an error because its valuewas borrowed", theOwner);
}


fn displayValue(borrower: &String) {
  println!("This value is borrowed and cant be muitated {}", borrower );
}

In the code above we can see that when we call the method displayValue(&theOwner) there is an '&' included to the argument passed, this signifies borrowing in rust. Also, we need to also add the '&' before the type argument in function/method declaration fn displayValue(&borrower: &String). This signifies that newOwner is a borrowed string and it borrowed it from theOwner.

Analogy of borrowing:

Let's say you are given an assignment in class to write an article, then your classmate who is stuck wanted some inspiration and asked you for your article. Because you are a nice person, and you want to help out, you decided to share with her or him, a Google doc link to your article. On the Google doc, you ensured you gave him or her permission to only read your article but not edit or delete.

This explains what borrowing is and if you noticed, you can only read but not edit or delete, what this means in rust is that you can not mutate a referenced variable. Let say, instead of displayValue(&borrower) you want to editValue(borrower: &String), this won't work because you only have immutable access to the value.

fn main() {
  let theOwner = String::from("value");
  displayValue(&theOwner);

  println! ("the owner {} will not throw an error because its value was borrowed", theOwner);
}


fn editValue(borrower: &String) {
  println!("This value is borrowed and cant be muitated{}", borrower +" String" );
}

To solve the problem, Rust provides a mutable borrowing.

Mutable Borrowing:

Mutable borrowing allows you to mutate the value passed. Now let's say it's a group assignment, a group of 2 people each we're asked to write an article and luckily you got to pair with your best friend. Unfortunately, your best friend was kinda sick so you had to do the article all by yourself. Before the day of submission, your friend got better and requested access to the article then you decided to share with her/him a link to the Google doc also permitting him/her to edit and delete. Changes he/she made will be recorded in the article.

fn main() {
  let theOwner = String::from("value");
  displayValue(&mut theOwner);

}


fn editValue(borrower: &mut String) {
  println!("This value is borrowed and can be muitated {}", borrower +" String" );
}

Here our code changed slightly, instead of using just '&' we now have a '&mut' keyword. The result of this is that you can mutate the string "Value".

Just like ownership, Borrowing has rules too.

  1. You can either have many immutable references borrowing or just one mutable reference borrowing
  2. References must always be valid
//First rule A: you can use reference borrowing 
//many times as far as the main owner is valid.
//P1
fn main() {
  let theOwner = String::from("value");

  borrowOne(&theOwner);
  borrowTwo(&theOwner);
  borrowThree(&theOwner);
}


fn borrowOne(borrower: &String) {
    println!("This value is borrowed and cant be mutated  {}", borrower);
}
fn borrowTwo(borrower: &String) {
   println!("This value is borrowed and cant be mutated {}", borrower );
}
fn borrowThree(borrower: &String) {
   println!("This value is borrowed and cant be muitated {}", borrower );
}

//First rule B: you can use a mutable borrowing 
//just one time and reference borrowing won't work here.
//P2

fn main() {
  let theOwner = String::from("value");

  borrowOne(&mut theOwner);
  borrowTwo(&mut theOwner) //This will throw our an error.
  borrowThree(&mut theOwner) //This will throw our an error.
}


fn borrowOne(borrower: &mut String) {
    println!("This will work because theOwner has been borrowed here  {}", borrower +" String" );
}
fn borrowTwo(borrower: &mut String) {
   println!("This wont work because theOwner has been borrowed 
by borrowOne {}", borrower +" String" );
}
fn borrowThree(borrower: &mut String) {
   println!("This wont work because theOwner has been borrowed 
by borrowOne {}", borrower +" String" );
}

//Second rule, References must be valid
//p1:
fn main() {
  let theOwner = String::from("value");
drop(theOwner);
  displayValue(&mut theOwner);
}


fn editValue(borrower: &mut String) {
  println!("This wont work because theOwner variable has dropped its 
value making it invalid after the drop() {}", borrower +" String" );
}

On a final note, when we compile a rust program, the compiler runs through a phase called the borrow-checker where it checks if values borrowed are still valid when the reference is used. I hope you now understand these few rust concepts, you can follow me on Twitter where I tweet about rust.