
Cloud Native Pilgrim | Kubernetes Enthusiast | Serverless Believer | Customer Experience Architect @ Pulumi | (he/him) | CK{A,AD} |
Search for a command to run...

Cloud Native Pilgrim | Kubernetes Enthusiast | Serverless Believer | Customer Experience Architect @ Pulumi | (he/him) | CK{A,AD} |
No comments yet. Be the first to comment.
Discover the power of Rust - the modern systems programming language - through my insightful blog series. Dive deep into its features, benefits, and examples.
TL;DR Code https://github.com/dirien/rust-jreleaser Introduction Recently I decided to start learning Rust 🦀! https://twitter.com/_ediri/status/1577868970593583104?s=20&t=0NWRHqD7ohV8drrQyYVkuQ And of course, one of the important parts for me was: H...
GPU scheduling in Kubernetes has always felt like buying a mansion when you need a studio apartment. A small inference workload that needs 2GB of GPU memory gets scheduled on an entire 80GB A100, and there's nothing you can do about it. The device pl...

TL;DR: The code https://github.com/dirien/quick-bites Nothing is more controversial in the Kubernetes community than whether to use Helm or Kustomize. I always advocate the philosophy of using the right tool for the right job. It avoids the problem...

TL;DR Le code https://github.com/dirien/quick-bites Introduction This article is part three of my series on secret management on Kubernetes with the help of Pulumi. In my first article, we talked about the Sealed Secrets controller. The second arti...

With AWS Lambda

Disaster Recovery, Data Migration made easy!

How to increase your fluency in a programming language? You have to read a lot of it! Very lot. But how can you read a lot, when you lack the knowledge to understand what you read.
In this article, I try to cover as many keywords and symbols as possible using Rust snippets.
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
A semicolon marks the end of a statement:
let x = 3;
let y = 5;
let z = y + x;
This means that statements can span over multiple lines:
let x = vec![1, 2, 3, 4, 5, 6, 7, 8]
.iter()
.map(|x| x + 3)
.fold(0, |x, y| x + y);
let introduce a new set of variables into the current scope
let x;
x = 42;
You can write this as a single line:
let x = 42;
Variables in Rust are immutable by default, and require the mut keyword to be made mutable.
let mut x = 42
Types can be annotated
let x: i32;
x = 42;
And again, you can write this as a single line
let x: i32 = 42;
When in doubt: Just use i32 for everything! i32 is the default in Rust
You can declare a name and initialize it later but you can not access uninitialized variables
let x;
foobar(x); // error: borrow of possibly-uninitialized variable: `x`
x = 42;
Doing it this way is completely fine:
let x;
x = 42;
foobar(x);
The underscore _ is a special symbol. It basically means that something is ignored:
// this does *nothing* because 42 is a constant
let _ = 42;
// this calls `get_thing` but throws away its result
let _ = get_thing();
Rust has tuples, which are a fixed-length collections of values of different types.
let pair = ('a', 17);
pair.0; // this is 'a'
pair.1; // this is 17
We could write, with explicit type annotation:
let pair: (char, i32) = ('a', 17);
Tuples can be destructured when doing an assignment:
let (some_char, some_int) = ('a', 17);
// now, `some_char` is 'a', and `some_int` is 17
This is very useful when a function returns a tuple:
let (left, right) = slice.split_at(middle);
You can use underscore _ to throw away a part of it:
let (_, right) = slice.split_at(middle);
To declare a function, you use fn.
Here is a void function:
fn greet() {
println!("Hi there!");
}
And this is a function that returns a 32-bit signed integer. The arrow indicates its return type!
fn fair_dice_roll() -> i32 {
4
}
Functions can be generic:
fn foobar<T>(arg: T) {
// do something with `arg`
}
You can have multiple type parameters:
fn foobar<L, R>(left: L, right: R) {
// do something with `left` and `right`
}
Bracket pairs do declare a block. A block has its own scope:
// This prints "in", then "out"
fn main() {
let x = "out";
{
// this is a different `x`
let x = "in";
println!("{}", x);
}
println!("{}", x);
}
Keep in mind that Blocks are also expressions. This means they evaluate to a value.
// this:
let x = 42;
// is equivalent to this:
let x = { 42 };
There can be multiple statements inside a block:
let x = {
let y = 1; // first statement
let z = 2; // second statement
y + z // this is the *tail* - what the whole block will evaluate to
};
When you omit the semicolon at the end of a function, it is the same as returning:
fn fair_dice_roll() -> i32 {
return 4;
}
fn fair_dice_roll() -> i32 {
4
}
if conditions are expressions too:
fn fair_dice_roll() -> i32 {
if feeling_lucky {
6
} else {
4
}
}
And a match is also an expression and not a statement!
fn fair_dice_roll() -> i32 {
match feeling_lucky {
true => 6,
false => 4,
}
}
Dots . are used to access fields of a value:
let a = (10, 20);
a.0; // this is 10
let amos = get_some_struct();
amos.nickname; // this is "fasterthanlime"
Or to call a method on a value:
let nick = "fasterthanlime";
nick.len(); // this is 14
The double-colon, ::, is similar, but it operates on namespaces.
In this example, std is a crate (~ a library), cmp is a module (~ a source file), and min is a function:
let least = std::cmp::min(3, 8); // this is 3
A Struct is declared with the struct keyword
struct Number {
odd: bool,
value: i32,
}
And they can be initialized using literals
let one = Number { odd: true, value: 1 };
let two = Number { odd: false, value: 2 };
You can declare methods on your own types:
impl Number {
fn is_strictly_positive(self) -> bool {
self.value > 0
}
}
Structs can be generic too:
struct Pair<T> {
a: T,
b: T,
}
fn print_type_name<T>(_val: &T) {
println!("{}", std::any::type_name::<T>());
}
fn main() {
let p1 = Pair { a: 3, b: 9 };
let p2 = Pair { a: true, b: false };
print_type_name(&p1); // prints "Pair<i32>"
print_type_name(&p2); // prints "Pair<bool>"
}
A vector is a container that stores the values like an array, but it has more advantages than an array data structure. A vector can increase it's size dynamically during runtime.
It is provided by the standard library and is a generic.
fn main() {
let mut v1 = Vec::new();
v1.push(1);
let mut v2 = Vec::new();
v2.push(false);
print_type_name(&v1); // prints "Vec<i32>"
print_type_name(&v2); // prints "Vec<bool>"
}
While we talked about Vectors, let's check the vec! macro:
fn main() {
let v1 = vec![1, 2, 3];
let v2 = vec![true, false, true];
print_type_name(&v1); // prints "Vec<i32>"
print_type_name(&v2); // prints "Vec<bool>"
}
All types of name!(), name![] or name!{} are invoking a macro. Macros just expand to regular code.
You already know a famous macro: println!
fn main() {
println!("{}", "Hello there!");
}
This macro expands to:
fn main() {
use std::io::{self, Write};
io::stdout().lock().write_all(b"Hello there!\n").unwrap();
}
Rust provides a loop keyword to indicate an 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;
}
}
Or you can use the while loop. As the name stated, this loops runs while a condition is true.
fn main() {
// A counter variable
let mut n = 1;
// Loop while `n` is less than 101
while n < 101 {
if n % 15 == 0 {
println!("fizzbuzz");
} else if n % 3 == 0 {
println!("fizz");
} else if n % 5 == 0 {
println!("buzz");
} else {
println!("{}", n);
}
// Increment counter
n += 1;
}
}
Anything that is iterable can be used in a for in loop. One of the easiest ways to create an iterator is to use the range notation a..b.
fn main() {
// `n` will take the values: 1, 2, ..., 100 in each iteration
for n in 1..101 {
if n % 15 == 0 {
println!("fizzbuzz");
} else if n % 3 == 0 {
println!("fizz");
} else if n % 5 == 0 {
println!("buzz");
} else {
println!("{}", n);
}
}
}
But for in it also works with a Vec:
fn main() {
for i in vec![52, 49, 21] {
println!("I like the number {}", i);
}
}
Or a slice:
fn main() {
for i in &[52, 49, 21] {
println!("I like the number {}", i);
}
}
Or an actual iterator:
fn main() {
// note: `&str` also has a `.bytes()` iterator.
// Rust's `char` type is a "Unicode scalar value"
for c in "rust".chars() {
println!("Give me a {}", c);
}
}
You can catch the failure of some parts of your program instead of calling panic!. This can be done using the Option enum.
The Option<T> enum has two variants:
None, to indicate failure or lack of value, andSome(value), a tuple struct that wraps a value with type T.enum Option<T> {
None,
Some(T),
}
impl<T> Option<T> {
fn unwrap(self) -> T {
// enums variants can be used in patterns:
match self {
Self::Some(t) => t,
Self::None => panic!(".unwrap() called on a None option"),
}
}
}
use self::Option::{None, Some};
fn main() {
let o1: Option<i32> = Some(128);
o1.unwrap(); // this is fine
let o2: Option<i32> = None;
o2.unwrap(); // this panics!
}
Sometimes it is important to display why an operation failed. To do this you can use the Result enum.
The Result<T, E> enum has two variants:
use std::fs::File;
fn main() {
let greeting_file_result = File::open("hello.txt");
let greeting_file = match greeting_file_result {
Ok(file) => file,
Err(error) => println!("Problem opening the file: {:?}", error),
};
}
I hope, I could give a got overview about Rust in under 10 mins. Of course, this is just the beginning of a wonderful journey in the word of Rust. For more Rust material, check out the following links: