I think it was your deliberate decision to choose Rust. It’s not so popular programming language as JavaScript, or PHP, or Java, or whatever else. But you chose Rust, and I respect your decision.
I can tell you why I started to learn Rust. I decided to develop a decentralized product on the Solana blockchain. The only solution for Solana is Rust. You can actually use C there, but it’s not recommended. So I decided to start learning Rust and I am very happy with it. I want to tell you more about Rust Programming Fundamentals. It took much time for me to find and structurize this info, so if it can help anyone - I would be satisfied.
Cargo
Cargo is a package manager, build system, test runner, docs generator, etc. It’s like all of the good parts of npm, pip, bundler, and make.
To create a new project, just run:
cargo new hello
To run the project, just run:
cargo run
And to make a release build, run:
cargo run --release
Variables in Rust
To declare a variable, you use "let"
let bunnies = 10;
Or you can use a tuple if you want to declare multiple variables at once.
let (bunnies, carrots) = (10, 50);
Rust has immutable variables by default. I think it’s not actually comfortable to use, but who cares. They tell that it’s for "Safety, Concurrency, and Speed".
To declare a mutable variable, you should use "let mut".
let mut bunnies = 10;
bunnies = 20;
Also, you can use constants in Rust. The convention is to use all uppercase words separated with underscores in constants. The type annotation is required. And value must be a constant expression that can be determined at compile time.
const BEST_LANGUAGE_IN_THE_WORLD: &str =
"rust
";
Memory Safety
Rust guarantees memory safety at compile time. As a part of that, variables must be initiated before you use them.
fn main() {
let x: i32;
println!(
"{}", x); // Error!
}
This code will not even compile!
Functions in Rust
fn sum(a: i32, b: i32) -> i32 {
a+b
}
This works fine. If you don’t add the semicolon to the last expression in a block, then it will be returned as the value of the block. This is called the tail expression.
In other words…
{ return true; }
and
{ true }
are the same.
Scalar Types
Integer types
Unsigned: u8, u16, u32, u64, u128, usize
Signed: i8, i16, i32, i64, i128, isize
In integer types, underscores are ignored when you define the value.
For example, 1000000 and 1_000_000 are the same.
Floating Point types: f32, f64. f64 is the default, but it’s slower than f32.
let x: u16 = 5;
let y: f32 = 3.14;
Booleans are true or false. BOOLEANS ARE NOT INTEGERS. So don’t try to compare them.
Character type. It’s always 4 bytes. Character literals use single quotes. And characters are useless in Rust. Character is not a part of the string.
let my_letter = 'a';
Compound types
Tuple
let info: (u8, f64, i32) = (1, 3.3, 999);
To access members of a tuple you can use dots syntax.
let jets = info.0;
let fuel = info.1;
let ammo = info.2;
Or get them all at once
let (jets, fuel, ammo) = info;
Array
let buf: [u8; 3] = [1, 2, 3]; // specify values
or
let buf: [u8; 3] = [1, 3]; // specify the value and the count
Control flow
if num == 5 {
msg = "five";
} else if num == 4 {
msg = "four";
} else {
msg = "other";
}
And in Rust, we can change this code this way
msg = if num == 5 {
"five"
} else if num == 4 {
"four"
} else {
"other"
};
How to break out of a nested loop? Easy!
‘loop1: loop {
loop {
loop {
break 'loop1;
}
}
}
while loop is exactly similar.
while should_end_loop() {
// do something
}
And we can write it like this:
loop {
if !should_end_loop() { break; }
// do something
}
I prefer "while" loop, but it’s for you to decide what to use.
One more thing here…
"For" loop can take a pattern:
let array = [(1, 2), (3, 4)];
for (x, y) in array.iter() {
// do something with x and y
}
Strings
There are at least 6 types of strings in the Rust std library. But we mostly care about 2 of them.
The first is str
. You will probably see it as &str
.
let msg = "Hello World";
The second one is String. The data in str can not be modified. But the data in a String, can be modified.
let msg: String = "Hello World".to_string();
or
let msg: String = String::from("Hello World");
To access the vector of the UTF-8 bytes of a String, you can use
word.bytes();
To access chars, use
word.chars();
Ownership
Ownership in Rust is amazing. It makes Rust so different from other programming languages. And it makes those crazy safety guarantees possible.
It has 3 rules of ownership:
Rule #1. Each value has an owner.
Rule #2. Only one owner of a value.
Rule #3. Value gets dropped if its owner goes out of scope.
let s1 = String::from("abc");
let s2 = s1; // it's not a copy! value is moved.
println!("{}", s1); // Compiler Error!
Reference & Borrowing
let mut s1 = String::from("abc");
do_something(&mut s1); // it borrows the reference to the value
fn do_something(s: &mut String) {
s.insert_str(0, "Hello, ");
}
Structs (Classes in Rust)
Struct in Rust could have variables, methods, and associated functions.
struct User {
name: String,
email: String,
}
You can do implementation inside structs.
impl User {
fn new(name: String, email: String) -> Self {
Self {
name: name,
email: email,
}
}
}
let user = User::new(String::from("Mike Timashov"), String::from("info@miketimashov.com"));
Inheritance in Rust
It’s going to be a rather short block because there is no inheritance in Rust! So is Rust Object-Oriented language? Good question.
But don’t be in a hurry to stop learning Rust. Continue reading.
Traits (Interfaces in Rust)
Traits in Rust are similar to Interfaces in other languages.
Let’s create a trait.
struct User {
name: String,
email: String,
}
trait Move {
fn start_moving(&self) -> bool;
}
impl Move for User {
fn start_moving(&self) -> bool {
true
}
}
Each method of a trait should be implemented.
Collections
Vector
It’s the most commonly used collection.
let mut v: Vec<i32> = Vec::new();
v.push(2);
v.push(4);
v.push(6);
let x = v.pop(); // x is 6
println!("{}", v[1]); // prints "4"
You can also create a vector much more ergonomic, like this:
let mut v = vec![2, 4, 6];
Vectors have a ton of methods.
HashMap<K, V>
In other languages, you can call it a dictionary.
let mut h: HashMap<u8, bool> = HashMap::new();
h.insert(2, true);
h.insert(7, false);
You must specify a type of Key and a type of Value.
There are a bunch of other collections: VecDeque, HashSet, LinkedList, BinaryHeap, BTreeMap, BTreeSet. You can read about them in Rust docs. I use them rarely, so I don’t want to waste time here to describe each of them.
Enums in Rust
Enums in Rust are really powerful!
enum Color {
Red,
Green,
Blue,
}
let color = Color::Red;
But the most power from enums in Rust comes from associated data and methods with the variants.
enum SomeItem {
Empty,
Ammo(u8),
Things(String, u32),
Location {x: i32, y: i32},
}
use SomeItem::*;
let item = Empty;
let place = Place {x: 0, y: 0};
Isn’t this amazing?
Even better, you can implement methods with an enum.
impl SomeItem {
fn display(&self) {}
}
And you can use enums with generics.
enum Option<T> {
Some(T),
None,
}
Closures in Rust
I guess you already know what closures are. I will just show you a few examples of how to use them in Rust.
let add = |x, y| { x + y }
add(1, 2); // returns 3
And another example:
let s =
"Hello World
".to_string();
let f = move || {
ptintln!(
"{}
", s);
};
f(); // prints Hello World
Threads in Rust
Here is a fully functional, but empty example of threads in Rust.
use std::thread;
fn main() {
let handle = thread::spawn(move || {
// do stuff in a child thread
});
// do stuff simultaneously in the main thread
// wait until thread has exited
handle.join().unweap();
}
Final Words
What do you think about this article? Should I write more about my experience with Rust, other programming languages, different platforms, my blockchain experience, etc?
Please write your comments and subscribe to my newsletter.