Rust has had its beginnings in the labs of Mozilla Research. This open source programming language is extremely fast and has become very popular in recent years.
As defined by the developers themselves, Rust is a systems programming language that runs blazingly fast, prevents segfaults, and guarantees thread safety. Rust is sponsored by Mozilla Research which describes it as a safe, concurrent and practical language.
Why learn Rust?
The goal of those who developed Rust was for it to be a good language for creating highly concurrent and safe systems. Rust is also designed to provide speed and safety, at the same time. Due to these features, Rust has held the title of ‘Most Loved Programming Language’ in the Stack Overflow Developer survey for three straight years, starting from 2016 to 2018.
What is so unique about Rust?
Rust is intended to be a language for highly concurrent and safe systems and “programming in the large,” that is, creating and maintaining boundaries that preserve large-system integrity. This has led to a feature set with an emphasis on safety, control of memory layout and concurrency. The performance of idiomatic Rust is comparable to the performance of idiomatic C++. Some other unique features of Rust are:
- Minimal runtime
- Efficient C bindings
- Efficient thread handling without race conditions between the shared data
- Guaranteed memory safety
- Type inference
Let’s begin with ‘Hello World!’
In the age-old tradition of programming, let’s first greet the world with a ‘Hello’, i.e., a ‘Hello World’ program, as given below:
fn main() { println!("Hello, world!"); }
There is quite a lot going on in these two lines of code. Let’s begin with fn, which is how a function is defined in Rust. main is the entry point of every Rust program. println! prints text to the console and its ! indicates that it’s a macro instead of a function. A macro in Rust is a way to encapsulate patterns that simply can’t be done using a function abstraction.
Coding in Rust
There are some more ways you can use the println! macro, as shown below:
fn main() { println!("{}, {}!", "Hello", "world"); // Hello, world! println!("{0}, {1}!", "Hello", "world"); // Hello, world! println!("{greeting}, {name}!", greeting="Hello", name="world"); // Hello, world! println!("{:?}", [1,2,3]); // [1, 2, 3] println!("{:#?}", [1,2,3]); /* [ 1, 2, 3 ] */ // format! macro is used to store the formatted STRING let x = format!("{}, {}!", "Hello", "world"); println!("{}", x); // Hello, world! }
Data types in Rust
A few basic data types in Rust are:
- bool: To define true or false values
- char: A single character value
- Fixed size (bit) signed (+/-) integer types: Denoted as i8, i16, i32, i64 where the number after i represents the bit size
- Fixed size (bit) unsigned (+) integer types: Denoted as u8, u16, u32, u64 where the number after u represents the bit size
- Variable sized signed (+/-) integer: Represented as isize in code. Covers all signed integer types
- Variable sized unsigned(+) integer: Represented as usize in code. Covers all unsigned integer types
- 32-bit floating point: Represented as f32 in code
- 64-bit floating point: Represented as f64 in code
- Arrays: Fixed size list of elements of the same data type. Arrays are immutable by default and even with mut, their element count cannot be changed
- Tuples: Fixed size ordered list of elements of different (or the same) data types. Tuples are also immutable by default and even with mut, their element count cannot be changed. Also, if you want to change an element’s value, the new value should have the same data type of the previous value
- Slice: Used to create a reference to only part of another data structure
- Str: Unsized UTF-8 sequence of Unicode string slices
Examples of the above data types are shown below:
let a : bool = true; // bool type let x = ‘x’; // char type let num: i16 = 12 // fixed size signed integer type let unum: u16 = 13 // fixed size un-signed integer type let a = [1, 2, 3]; // a[0] = 1, a[1] = 2, a[2] = 3 // array type let a = (1, 1.5, true, ‘a’, “Hello, world!”); // tuple type // example of slice let a: [i32; 4] = [1, 2, 3, 4];//Parent Array let b: &[i32] = &a; //Slicing whole array let c = &a[0..4]; // From 0th position to 4th(excluding) let d = &a[..]; //Slicing whole array // example of Str datatype let a = “Hello, world.”; //a: &’static str
Variable bindings, constants and statics in Rust
In the domain of Rust, the variables are immutable by default; hence, they are called variable bindings. To make them mutable, the mut keyword has been used. Rust is a statically typed language, but it doesn’t need to define the data type while declaring a variable binding. The compiler is smart enough to figure out the best data type after checking the usage. The only exception to this rule is when you declare the constants and static variables. While declaring constants and statics, you must declare the type using a colon (:). Types come after the colon.
Examples for variable bindings are given below:
let a = true; // without specific declaration of type let b: bool = true; // with specific declaration of type let mut z = 5; // mutable variable binding
The let keyword is used in binding expressions. We can bind a name to a value or a function.
An example of constants is shown below:
const const_1: i32 = 5;
The const keyword is used to define constants.
An example of statics is given below:
static static_1: bool = true;
The static keyword is used to define the ‘global variable’ type facility.
One noteworthy difference between static and const is that the former has a fixed location in memory while const doesn’t.
Functions in Rust
Functions are declared with the keyword fn. An example of a function in Rust is given below:
fn main() { println!(“Hello, world!”); }
When passing arguments to a function, it is imperative that the data types of the arguments are declared, as shown in the example below:
fn print_sum_of_numbers(num1: i8, num2: i8) -> i32 { println!(“addition is: {}”, num1 + num2); num1+ num2 //no ; means an expression, return num1+num2 }
In Line 1 of the above snippet, ->i32 determines the return type of the value returned by the function print_sum. In Rust, the function body can be assigned to a variable, described as function pointers, as shown in the snippet below:
// Function pointers fn pointerExample(count: i32) -> i32 { count + 1 //no ; means an expression, return count +1 } let count1= pointerExample; let count2 = count1(5); //6
Control flows in Rust
A wide array of control flows is available in Rust, including popular ones like if-else, for and while loops, as well as some control flows exclusive to Rust, i.e., match and loop. An example of these is shown in the code snippet below:
// if-else Example let comp_size = 7; if comp _size < 5 { println!(“Small Company”); } else if comp _size < 10 { println!(“Medium Company”); } else { println!(“Large Company”); } // while loop example let mut count= 0; while count < 5 { if count == 0 { println!(“Omit value : {}”, b count count += 1; continue; } else if count == 2 { println!(“exit At : {}”, count); break; } println!(“value : {}”, count); count += 1; } // for Loop Example for count in 0..10 { if count == 0 { println!(“Omit Value : {}”, count); continue; } else if count == 2 { println!(“Exit At : {}”, count); break; } println!(“ value : {}”, count); }
Another unique control flow available in Rust is loop, which is used to indicate an infinite loop, out-of-the-box. The break statement can be used to exit a loop at any time, whereas the continue statement can be used to skip the rest of the iteration and start a new one, as shown in the code snippet below:
fn main() { let mut count = 0u32; println!(“Let’s count until infinity!”); // Infinite loop loop { count += 1; if count == 3 { println!(“three”); // Skip the rest of this iteration continue; } println!(“{}”, count); if count == 5 { println!(“OK, that’s enough”); // Exit this loop break; } } }
Last but not the least, let us discuss ‘match’. Rust provides pattern matching through the match keyword, which can be used in switch statements in C. An example of match is shown in the snippet below:
fn main() { let number = 13; // TODO ^ Try different values for `number` println!(“Tell me about {}”, number); match number { // Match a single value 1 => println!(“One!”), // Match several values 2 | 3 | 5 | 7 | 11 => println!(“This is a prime”), // Match an inclusive range 13...19 => println!(“A teen”), // Handle the rest of cases _ => println!(“Ain’t special”), } let boolean = true; // Match is an expression too let binary = match boolean { // The arms of a match must cover all the possible values false => 0, true => 1, // TODO ^ Try commenting out one of these arms }; println!(“{} -> {}”, boolean, binary); }
This concludes our exploration of the basics of the Rust programming language. Rust has many more unique features to offer, like custom macros, which are beyond the scope of this article. To get more details about these, official Rust documentation can be referred to.