WHY RUST? (Episode 1 )

Rust is a systems programming language that runs at gunfire speed, prevents segfaults, and guarantees thread safety.”

Rust & its paternity

First, Rust is heavily influenced by Cyclone (a safe dialect of C and an imperative language), with some aspects of object-oriented features from C++. But, it also includes functional features from languages like Haskell and OCaml. The result is a C-like language that supports multi-paradigm programming (imperative, functional, and object oriented).

blog pic1
Figure 1. Rust and its family tree

Key Features of Rust

Rust has many features that make it useful, but developers and their needs differ. I cover four of the key concepts that make Rust worth learning.

1. Immutability By Default

An immutable object is one whose state cannot and will not change after it’s initial creation. Immutable objects are great, mostly because they are Thread Safe.

All variables and references are immutable by default, providing  an advantage of concurrency

Example :

fn main() {    
   let x = 5;
   println!("The value of x is: {}", x);    
   x = 6;
    println!("The value of x is: {}", x);
 }

Save and run the program using Cargo (build tool for rust). You should receive an error message, as shown in this output:

error[E0384]: cannot assign twice to immutable variable `x`  --> src/main.rs:4:5   | 2 |     let x = 5;   |         - first assignment to `x` 3 |     println!("The value of x is: {}", x); 4 |     x = 6;   |     ^^^^^ cannot assign twice to immutable variable

The error indicates that the cause of the error is that you cannot
assign twice to immutable variable x
because you tried to assign a second value to the immutable variable.

But you can make them mutable by adding mut keyword in front of the variable name.

Example:

fn main() {  
  let mut x = 5;     
  println!("The value of x is: {}", x);     
  x = 6;     
  println!("The value of x is: {}", x); }

When we run the program now, we get this:

$ cargo run    Compiling variables v0.1.0 (file:///projects/variables)     Finished dev [unoptimized + debuginfo] target(s) in 0.30 secs      Running `target/debug/variables` The value of x is: 5 The value of x is: 6

2. Ownership

Ownership is Rust’s most unique feature, and it enables Rust to make memory safety guarantees without needing a garbage collector.

All programs have to manage the way they use a computer’s memory while running. Rust uses a special approach: memory is managed through a system of ownership with a set of rules that the compiler checks at compile time.

Within Rust, variable bindings have a concept of ownership over what they are bound to. Logically when an entity is bound to a label, or variable, when the variable goes out of scope, Rust will free the bound resource.

By enforcing this rule very strictly, every time a variable goes out of scope it’s bound resource is deterministically freed. This protection allows us the language to guard against common errors such as double free errors which are common to other languages.

Stack

Heap

Easy for push & pull Keeping track of what parts of the code are using what data on the heap, minimizing the amount of duplicate data on the heap, and cleaning up unused data on the heap so you don’t run out of space are all problems that ownership addresses
Fast because of the way it accesses the data: it never has to search for a place to put new data or a place to get data from because that place is always the top Slow because you have to follow a pointer to get there
All data on the stack must take up a known, fixed size Data with a size unknown at compile time or a size that might change can be stored on the heap instead

Ownership Rules

  1. Each value in Rust has a variable that’s called its owner.
  2. There can only be one owner at a time.
  3. When the owner goes out of scope, the value will be dropped.

In addition to the deterministic freeing of bound resources, entities bound to a variable must follow the following rules:

  1. There can be zero or more immutable references
  2. There can be exactly one reference which is mutable

This means that you can either have as many immutable references to a variable as you want, OR you can have exactly one mutable reference ONLY. These rules enforced within the compiler allow guaranteed data race condition protections.

Data races occur when the following are true:

  1. Two or more pointers to the same resource
  2. At least one pointer is written
  3. There is no synchronization

As demonstrated by the rules, there can only ever be exactly one pointer to a resource at any time which is mutable. This check is performed at compile time. Since you cannot have an immutable pointer and a mutable pointer to the same variable binding at the same time there is no chance of a data race.

3. Type Inference

Rust has a special feature to infer the type of variable automatically at compile time.

let s = 5;

This line creates a new variable named s of integer type and binds it to the value 5.

Writing type-safe language while maintaining less boilerplate code is an important aspect of programming languages in terms of a developer’s productivity. Because the type-safe code is less error-prone and less boilerplate code leads to more readable code, both together means reduced development time. Type-inference is a great programming language feature that maintains this balance.

4. Pattern Matching

Rust has an extremely powerful control flow operator called match that allows you to compare a value against a series of patterns and then execute code based on which pattern matches. Patterns can be made up of literal values, variable names, wildcards, and many other things.

Example:

let some_u8_value = 0u8;
match some_u8_value {
    1 => println!("one"),
    3 => println!("three"),
    5 => println!("five"),
    7 => println!("seven"),
    _ => (),
}

When the match expression executes, it compares the resulting value against the pattern of each arm, in order. If a pattern matches the value, the code associated with that pattern is executed. If that pattern doesn’t match the value, execution continues to the next arm, much as in a coin-sorting machine. We can have as many arms as we need.

There is a complexity of using nested if let else if let and matches overcome those complexities. The downside of using if let else expressions are that the compiler doesn’t check exhaustiveness, whereas with match expressions it does. If we omitted the last else block and therefore missed handling some cases, the compiler would not alert us to the possible logic bug.

References :

 

Leave a Reply

%d bloggers like this: