Rust — Programming concepts

CSP
6 min readJan 2, 2021
Rust Logo

This article is intended for an audience who have at least one programming language background and wanted to get a high-level overview of key concepts of Rust language.

Rust is a statically typed language used for system programming and various applications like command-line tools, web services, DevOps tooling, embedded devices, machine learning, and even for web development.

Rust’s USP is performance and safety (memory and concurrency) with negligible runtime footprint unlike Java, Python, etc.

Common Programming concept

Variables are immutable by default. Need to explicitly put mut to have mutability

let a = 2;
a = 3; // compile-time error
let mut a = 2;
a = 3; // works

Constants are immutable. Aren’t allowed to use mut

const MAX:u32 = 2

Shadowing — reuse the same variable name. Not the same as mut as you can change the type of variable.

let s = "  "; // automatic type inference to string
let s = s.len(); // automatic type inference to int

Scalar Data types — has four primitive scalar types: integers, floating-point numbers, Booleans, and characters.

  • integers signed ( i)and unsigned ( u ) from 8 to 128 bits. (e.g u8, u16 etc)
  • floating-point numbers — floating-point types are f32 and f64 . Default to f64.
  • boolean type — denoted by bool
  • char type — denoted by char . value wrapped by a single quote

Compound data types — can group multiple values into one type. Rust has two primitive compound types: tuples and arrays.

  • tuples — have a fixed length: once declared, they cannot grow or shrink in size
let tup: (i32, f64, u8) = (500, 6.4, 1);

supports destructuring

let (x, y, z) = tup;
  • array — every element of an array must have the same type. Arrays in Rust are different from arrays in some other languages because arrays in Rust have a fixed length, like tuples.

Functions — function definition starts with fn . The function body can contain statements (ending with ; ) or expression (without ; );

fn add_numbers(a:i32, b:i32) -> i32 {
return a + b;
}or,fn add_numbers(a:i32, b:i32) {
a + b //expression. dont need return
}fn main() {
add_numbers(5, 6);
}

Note: statements don’t return values unlike in many other languages.

// throws compiler error
let a = (let b = 1);
// this is valid
let a = {
let x = 3; // statement
x + 1 // expression without ending semi colon
};

Control Flow

if else block — condition should be explicitly evaluated to boolean. Unlike JavaScript, rust doesn't do auto-conversion to bool type

let a = 1;
let mut b = "Greater than or equal to two";
if a < 2 {
b = "Less than 2"

}
or,let b = if a < 2 { "Less than 2" } else { "Greater than or equal to two" };//throws compiler error
if a {
}

loop — infinite iteration until a breakis encountered

let mut c = 100;
let result = loop {
c -= 1;
if c == 0{
break c;
}
};

while — conditional loop

let mut c = 100;
while c != 0 {
c -= 1;
};

//while is not good for iteration as it is prone to bug and rust needs to add runtime check everytime accessing a collection index.
let a = [10, 20, 30, 40, 50];
let mut i = 0;
while i < 5 {
println!("the value is: {}", a[i]);
i+=1;
}

forthe best way to loop over the collection or even countdown.

let a = [10, 20, 30, 40, 50];      
for element in a.iter() {
println!("the value is: {}", element);
}

This is a much safe code as the iteration length is derived. You cannot do this in while while i < a.iter() won’t work as it returns an iterator and not a number.

Rust also supports Structs and Enums

Structs — Custom datatypes with fields and methods. Instantiated and accessed through fields defined in the struct.

struct User {
name:String,
email: String
}
fn main() {
//instance
let _user1 = User {
name: String::from("hello"),
email: String::from("hello@hello.com"),
};
println!("{}", _user1.name);
}

Write annotations to print the complete struct instance

#[derive(Debug)] //annotation
struct User {
name:String,
email: String
}
fn main() {
//instance
let _user1 = User {
name: String::from("hello"),
email: String::from("hello@hello.com"),
};
println!("user {:?}", _user1); /prints all fields
}

Defining Methods — Keyword impl is used to define a method for a struct.

#[derive(Debug)]
struct User {
name:String,
email: String
}
impl User {
fn hello(&self) { //fn hello(&self, param1:&ParamType) etc for multiple params
&self.name;
}

}
fn main() {
//instance
let _user1 = User {
name: String::from("hello"),
email: String::from("hello@hello.com"),
};
_user1.hello();
println!("user {:?}", _user1.name);
}

Functions follow the same rules as any other methods for ownership.

Associated Method — A method that works on the struct (and not the instance, think static)

Defined in implblock but don't have selfparam.

Called using :: on Struct

#[derive(Debug)]
struct User {
name:String,
email: String
}
impl User {
fn log(user:User) {
println!("user {:?}", user);
}

}
fn main() {
//instance
let _user1 = User {
name: String::from("hello"),
email: String::from("hello@hello.com"),
};
User::log(_user1);
}

Enums — Enum is defined by the keyword enum

enum Status {
INPROGRESS,
COMPLETE.
ERROR
}

accessed using :: .Status::INPROGRESS

The next section talks about Rust’s unique feature Ownership.

Ownership — A unique memory management approach govern by a certain set of rules for efficient garbage collection.

Data is stored in stacks or heaps.

Stack — used for known, fixed-size data. Follow LIFO. Pushing in the stack is faster.

Heap — For unknown or changing size. Allocate a location in heap and provides a pointer location for the address of the data. Accessing data in heap is slower than stack as you need to follow the pointer to access the data.

Rules of ownership —

Each value has a variable, owner.

Only one owner a time

When owner goes out of scope value is dropped

All primitive data types (integer, string, boolean, floating, char) and tuple with simple datatype are stored in the stack. String (not string)however is a complex data type that stores data in heap.

fn main() {
let mut s = String::from("hello");
s.push_str(", world!"); // push_str() appends a literal to a Stringprintln!("{}", s); // This will print `hello, world!`
}

In Rust, there isn’t a GC as such which keeps checking unused vars and free up memory location. As soon as the variable goes out of scope, Rust calls drop method frees up the memory. Calls automatically at the end of closing brackets.

What about multiple variables referencing the same location?

In the case of simple values, the data is `copied` over to another stack.

fn main() {
let mut _x = 5;
let _y = _x; //copy occurs because `_s1` has type `uint`, and has a `Copy` trait_x = 10;println!("{}",_y); //output 5. not 10println!("{}",_x); //output 10
}

For complex types, the reference is ‘moved’.

fn main() {
let mut _s1 = String::from("hello");
let mut _s2 = _s1; // `Move` occurs because `_s1` has type `String`, which does not implement the `Copy` trait_s1.push_str(", world!"); // throws error. Why? as _s1 is no longer the 'owner` of the data and the reference is `moved` to _s2 and _s1 will be dropped.println!("{}", _s2)
}

Passing variables in a function will have the same behavior based on the data type passed is simple or complex.

If you pass on String type to a function then the ownership is passed to the parameter. Returning values transfer back ownership.

Passing variables by reference

fn main() {
let mut _s1 = String::from("hello");
append(&mut _s1); //Need to specify a mutation reference by &mut
println!("{}", _s1)
}
fn append(s1:&mut String) { // also specify a accepting var as mutation reference
return s1.push_str(", world!");
}

You cannot define more than one mutable references to the same variable in the same scope

fn main() {
let mut _s1 = String::from("hello");
let _s2 = &_s1; //OK
let _s3 = &_s1; //OK
println!("{} {}", _s2, _s3) ;//OK
let _s4 = &mut _s1; //OK
let _s5 = &mut _s1; //NOT OK
println!("{} {}", _s4, _s5); //error
}//THIS WORKSfn main() {
let mut _s1 = String::from("hello");
{ //new scope
let _s4 = &mut _s1; //OK
println!("{}", _s4);
}
let _s5 = &mut _s1; // OK
println!("{}", _s5);//OK
}

This intro is a really concise version of some programming concepts that I learned from https://doc.rust-lang.org/. While learning, I do realize that the language is very strict (expect a lot of compile-time error) and rightly so to make it secure and performant.

To get a taste of it try https://play.rust-lang.org/ and run some of the examples.

If you enjoyed this story, please click the 👏 button and share to help others find it!

--

--

CSP

Striving to become my better self. I write on well researched thought provoking topics.