Getting Rusty!
If you were to ask me 2 weeks ago if I have any interest in learning a new programming language, I’d have outright said ‘No’. Not that I am any shy to learn new technologies or languages, but because I was too misguided in my own thinking that Python
for data science and Java
for resilient applications have become pretty much the status quo and that any innovation in the compute space has to be with compute clusters
and scalable databases
.
It was not until I decided to learn blockchain and smart contracts that I happened to cross paths with Rust
. Actually, my first stop was at Solidity
, a language on which Ethereum
blockchain is built. However, Solidity
didn’t sit well with me. The syntax felt verbose and documentation seemed scarce. Then along came Rust
and I am smitten with this language ever since.
To describe Rust
in one sentence: C++ efficiency and Java’s robustness, only better!!
I have a Github repo dedicated to all things Rust
learning here. Feel free to git clone and compose up to get Rusty
!
Hello world ….
fn main() {
println!("Hello world!");
}
Rust Resources
- The Book: Rust fundamentals to advanced concepts - all in one!
- Rust by Example: Rust concepts with demo code.
- Rust Doc: Rust Documentation.
- Rust Design Patterns
- Tokio: Async programming with Rust!
- Actix Web: Web programming with Rust!
- Async-Graphql: Building GraphQL APIs with Rust!
- Datafusion: Data science in Rust!
- LibP2P: Peer-to-Peer Applications in Rust!
I try to share some of the topics that I found intriguing in Rust
especially coming from Java
, Perl
and Python
background. An FYI - I will continually update this post with more and more snippets as I go.
Variables - Immutable, Mutables & Shadowing
fn main() {
let x = 1;
println!("{}", x); // prints 1
x = 2; // errors out since x is immutable
println!("{}", x);
}
Compilation error:
x = 2; // errors out since x is immutable
^^^^^ cannot assign twice to immutable variable
fn main() {
let mut x = 1;
println!("{}", x); // prints 1
x = 2; // it works since x is now mutable
println!("{}", x); // prints 2
}
fn main() {
let x = 1;
println!("{}", x); // prints 1
{
let x = 2; // shadowing
println!("{}", x); // prints 2
}
println!("{}", x); // prints 1
}
Constants
fn main() {
const MINUTES_IN_A_DAY: usize = 60 * 24; // this works
println!("{}", MINUTES_IN_A_DAY);
const SECONDS_IN_DAY: usize = get_seconds_in_day(); // this does not
println!("{}", SECONDS_IN_DAY);
}
pub fn get_seconds_in_day() -> usize {
return 60 * 60 * 24;
}
Compilation error:
const SECONDS_IN_DAY: usize = get_seconds_in_day();
calls in constants are limited to constant functions, tuple structs and tuple variants
Character
A char
is denoted by single quotes while String
by double quotes. A char
in Rust
is 4 bytes in size and can store not just ASCII but unicode values.
Tuples
A Tuple can be heterogeneous. A tuple has to be fixed in size. Tuple values are accessed using either dot notation or by unpacking. A tuple can be empty - (). Tuples get stored on stack.
fn main() {
let t: (i32, f32, String, char) = (10, 64.2, String::from("Hello"), '!');
println!("{}, {}, {}, {}", t.0, t.1, t.2, t.3);
}
Arrays
Arrays are homogeneous and are fixed in size. Arrays get stored on stack. Array elements are accessed using [], for example, array[0].
fn main() {
let array: [&str;2] = ["Jan", "Feb"];
println!("{:?}", array);
let array = [1,2,3,4,5]; // remember shadowing; otherwise compiler throws error as the variable declared as immutable
println!("{:?}", array);
println!("The length of array is: {}", array.len()); // prints 5
}
Vectors
Vectors are homogeneous and they get stored on heap, which means they can grow/shrink in size. The elements in vector can be accessed using [] or with get method, for example, v[0] or v.get(0).
fn main() {
let v = vec![1,2,3,4,5]; // implicit way of initializing a vector
println!("{:?}", v); // prints [1, 2, 3, 4, 5]
let mut v: Vec<String> = Vec::new();
v.push(String::from("Hello"));
v.push(String::from("world"));
println!("{:?}", v); // prints ["Hello", "world"]
// Accessing values from Vector
let hello: &str = &v[0]; // reference to 1st element of the vector
println!("{}", hello); // prints Hello
match v.get(0) {
Some(elem) => println!("{}", elem), // prints Hello
None => println!("Index out of bounds error."), // never get to this since 0th index is valid
}
// Let's try an index that doesn't exist
match v.get(100) {
Some(elem) => println!("{}", elem),
None => println!("Index out of bounds error."), // this gets invoked
}
}
Output:
[1, 2, 3, 4, 5]
["Hello", "world"]
Hello
Hello
Index out of bounds error.
Looping through Vector
using iterator
.
fn main() {
let mut v: Vec<i32> = vec![1,2,3,4,5];
// loop through using iterator
// read-only
for i in &v {
print!("{} ", i);
}
println!("");
println!("{:?}", v);
// mutate
for i in &mut v {
*i *= 2;
print!("{} ", i);
}
println!("");
println!("{:?}", v);
}
Output:
1 2 3 4 5
[1, 2, 3, 4, 5]
2 4 6 8 10
[2, 4, 6, 8, 10]
Though Vector
is homogeneous, we can hack using enums
to store heterogeneous data elements.
use std::fmt;
fn main() {
enum Person {
Age(i32),
Height(f64),
Name(String),
}
impl fmt::Display for Person {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *&self {
Person::Age(v) => write!(f, "Age: {}", v),
Person::Height(v) => write!(f, "Height: {}", v),
Person::Name(v) => write!(f, "Name: {}", v),
}
}
}
let dexter = vec![
Person::Name(String::from("Dexter Morgan")),
Person::Age(38),
Person::Height(5.11),
];
for info in &dexter {
println!("{}", info);
}
}
Output:
Name: Dexter Morgan
Age: 38
Height: 5.11
Strings
Rust's
core str
and String
standard library.
fn main() {
// core str
let s: str = "Hello there!"; // this errors out with: expected `str`, found `&str`
let s: &str = "Hello there!"; // this passes - string literal will always be used with `&str`
println!("{}", s);
let c = s; // two variables, s and c - both point to same `str` heap
println!("{}", s);
println!("{}", c);
// String library
let s: String = String::from("Hello there"); // proper ownership as opposed to borrowing/reference/pointer.
let c = &s; // borrowing
println!("{}", s);
println!("{}", c);
let c = s; // Move of ownership - NOT borrowing
println!("{}", c);
println!("{}", s); // this errors out with: ^ value borrowed here after move
}
Using +
operator and format!
macro.
fn main() {
let mut s: String = "Hello there".to_string();
s.push_str(", mate"); // appends a string literal
s.push('!'); // appends a character
let s1 = s; // Move of ownership; `s` is no longer valid.
println!("{}", s1);
let s2 = "How are you?";
let s3 = s1 + s2; // s1 is no longer valid; + operator acutally uses `fn add(self, s: &str) -> String;`
println!("{}", s3);
let format_string = format!("{}-{}-{}", "Hello there, mate!", s2, s3);
println!("{}", format_string);
println!("{}; {}", s2, s3); // s2 and s3 are still valid
}
Output:
Hello there, mate!
Hello there, mate!How are you?
Hello there, mate!-How are you?-Hello there, mate!How are you?
How are you?; Hello there, mate!How are you?
String
indexing and slices
fn main() {
let s = "Hello there".to_string();
let h = &s[0]; // errors out with: ^^^^ `String` cannot be indexed by `{integer}`
let sub_string = &s[0..5];
println!("{}", sub_string);
for c in s.chars() {
print!("{}", c);
}
println!("");
}
Output:
Hello
Hello there
String
indexing doesn’t work in Rust
because under the hood it uses Vec<u8>
. Since, they support UTF-8, a character can be more than 1 byte.
Hash Maps
(a.k.a Associative Arrays or Dictionaries)
Hashes get stored on heap. Hashes are homogeneous in nature.
use std::collections::HashMap;
fn main() {
let mut h = HashMap::new();
h.insert("john", "doe");
h.insert("jane", "doe");
println!("{:#?}", h);
// using zip method
let fnames: Vec<String> = vec![String::from("John"), "Jane".to_string()];
let lnames: Vec<String> = vec!["Doe".to_string(); fnames.len()];
let h: HashMap<_, _> = fnames.into_iter().zip(lnames.into_iter()).collect();
for (k, v) in &h {
println!("Key: {}; Value: {}", k, v);
}
// using get method
match h.get(&String::from("John")) {
Some(v) => println!("Value: {}", v),
None => println!("No such key."),
}
// word count example
let text: String = "Hello hello, how are you, mate?".to_string();
let mut word_count: HashMap<String, u32> = HashMap::new();
for s in text.to_lowercase().replace(",", "").split(" ") {
let count = word_count.entry(s.to_string()).or_insert(0);
*count += 1;
}
println!("{:#?}", word_count);
}
Output:
{
"jane": "doe",
"john": "doe",
}
Key: Jane; Value: Doe
Key: John; Value: Doe
Value: Doe
{
"mate?": 1,
"are": 1,
"hello": 2,
"how": 1,
"you": 1,
}
Enums
fn main() {
enum Person {
Name(String),
}
// implement a method on Person enum
impl Person {
fn greet(&self) -> () {
match self {
Person::Name(s) => println!("Hello, {}!", s),
}
}
}
let person1 = Person::Name("John Doe".to_string());
person1.greet(); // prints: Hello, John Doe!
}
fn main() {
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
println!("The value of Penny is: {} cents", value_in_cents(Coin::Penny));
println!("The value of Nickel is: {} cents", value_in_cents(Coin::Nickel));
println!("The value of Dime is: {} cents", value_in_cents(Coin::Dime));
println!("The value of Quarter is: {} cents", value_in_cents(Coin::Quarter));
}
Structs
Structs
and Enums
make up powerful features of Rust
.
use std::fmt;
fn main() {
struct Person {
name: String,
age: u32,
city: String
}
impl fmt::Display for Person {
fn fmt(self: &Self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} lives in {} and is {} years old.", self.name, self.city, self.age)
}
}
impl Person {
fn is_older(&self, other: &Person) -> bool {
return self.age > other.age
}
}
let p1 = Person {
name: String::from("Dexter Morgan"),
age: 38,
city: String::from("Miami"),
};
println!("{}", p1);
let p2 = Person {
name: String::from("Deborah Morgan"),
age: 32,
city: String::from("Miami"),
};
println!("{}", p1.is_older(&p2));
}
Output:
Dexter Morgan lives in Miami and is 38 years old.
true
Happy Coding in Rust
!!