J.D Nicholls
Founding Full-Stack Web3 Engineer at @baxusco | Digital nomad 🎒 | Mentor 👨🏫 | Speaker 🗣️ | Developer 👨💻 | Creator of @proyecto26 #opensource #developer
A brief introduction about Rust.
- Open Source Contributor 👨💻
- Game developer (Hobby) 🎮
- Developer who loves UX 💫
- Chocolate lover 🍫
- Founding Full-Stack Engineer 👷
AT
- Build reliable and efficient systems software:
• networking (web servers, mails servers, web browsers).
• compilers and interpreters.
• build games.
• command-line programs.
• web-assembly programs.
• applications for embedded devices.
Rust strikes a unique balance among performance, safety, and implementation expressions.
- Type safe: the compiler ensure the type of the variables.
- Memory safe: Rust pointers always refer to valid memory.
- Data race free: a value can't be mutated from multiple parts at the same time.
- Zero-cost abstractions: Rust allows the use of high-level concepts, like iteration, interfaces, and functional programming.
- Minimal runtime: no garbage collector to manage memory efficiently.
- Targets bare metal: write an operating system kernel or device drivers
Memory Allocation
The new variable has the ownership of the value; x can't be used again.
This transfers the reference string to a string variable; x is the new owner.
Immutability
fn main() {
let x = String::from("hi");
let y = &x; // <-- Immutable borrow
println!("{}", x);
println!("{}", y);
}
The new variable has the right to read
The Lifetime
- RAII (Resource Acquisition Is Initialization) - An analysis mechanism that can accurately calculate resource and variable life.
- C-Own, Java-GC, Rust-Ownership & Borrowing.
- Advantages of Rust: Fast as C & Safe as Java.
The Compiler
- The compiler does the job;
Source
Code
HIR
>
>
MIR
LLVM
IR
>
Machine
Code
>
Borrow checking,
Optimizations,
Code generation
Printing a string with params
fn main() {
let mut userId = 1;
println!("The user id is {}", userdId);
userId = 2;
println!("The new user id is {}", userdId);
}
Printing a hex value
fn main() {
let color: i32 = 0xff0000;
println!("The color red is #{:06x}", color);
}
Floats
fn main() {
let price = 5.0; // Float 64 bits
let version: f32 = 1.0 // Float 32 bits
}
fn main() {
let enabled = true;
let loading: bool = false;
}
Booleans
fn main() {
let environment = 'development';
let cool_emoji = '🤘'; // One character in Rust like 'a', 'A', etc
}
Strings
Arrays
fn main() {
let numbers = [1, 2, 3, 4, 5];
let fixed_size: [i32; 3] = [-1, 2, 3]; // same type
let first = fixed_size[0]; // signed integer (negative numbers)
}
fn main() {
let names = ("Juan", "David", "Nicholls Cardona");
println!("First name is {}", names.0);
let size = (1024, 768);
let (width, height) = size; // destructured when doing an assignment
println!("width: {}, height: {}", width, height);
// define the types
let pair: (char, i32) = ('a', 17);
}
Tuples
Tuples are fixed-length collections of values of different types
fn main() {
// reference string, you can't change it once you declare it
let name = "Juan";
let api_url: &'static str = "https://myapi.com"; // equivalent
}
Inmutable
fn main() {
let mut message = String::new();
message += "Testing";
let mut strng = String::from("ABC");
strng.push('D'); // one character
strng.push_str("EFG");
// assertion failed, expected 'G'
assert_eq!(strng.pop(), Some('F'));
}
Mutable
mut makes a variable binding mutable
#![allow(dead_code, unused)]
pub struct User {
username: String,
email: String,
active: bool,
}
fn main() {
// Initialized using struct literals
let mut user = User {
username: String::from("jdnichollsc"),
email: String::from("jdnichollsc@hotmail.com"),
active: true,
};
user.email = String::from("jdnichollsc@gmail.com");
println!("user email: {}", user.email);
}
Define a structure
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let blackColor = Color(0, 0, 0);
let origin = Point(0, 0, 0);
Define a tuple structure
#![allow(dead_code, unused)]
// enum with implicit discriminator starts at 0
enum Status {
Active,
Pending,
Inactive
}
struct User {
username: String,
status: Status,
}
fn main() {
let user = User {
username: String::from("jdnichollsc"),
status: Status::Active,
};
if matches!(user.status, Status::Active) {
println!("The user is active");
} else {
println!("The user is not allowed");
}
}
Define an enumeration
#![allow(dead_code, unused)]
// enum with explicit discriminator
enum Color {
Red = 0xff0000,
Green = 0x00ff00,
Blue = 0x0000ff,
}
struct User {
username: String,
theme: Color,
}
fn main() {
let user = User {
username: String::from("jdnichollsc"),
theme: Color::Red,
};
// Casting enum to integer (formatting)
println!("The user theme color is #{:06x}", user.theme as i32);
}
Define an enumeration
struct QuitMessage;
struct MoveMessage {
x: i32,
y: i32,
};
struct WriteMessage(String);
struct ChangeColorMessage(i32, i32, i32);
enum Message {
Quit(QuitMessage),
Move(MoveMessage),
Write(WriteMessage),
ChangeColor(ChangeColorMessage),
}
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
Combine enum with structs
Destruct an enum with structs
If Else condition
fn main() {
let age = 18
if age > 17 {
println!("Allowed");
} else {
println!("Not allowed");
}
}
While condition
fn main() {
let mut counter = 0;
while counter < 10 {
println!("Count: {}", counter);
counter += 1;
}
}
For condition
fn main() {
let colors = ["#A76B00", "#FFAF1E", "#126136"];
for color in colors.iter() {
println!("Color {}", color);
}
}
Loop condition
fn main() {
let mut counter: i32 = 0;
let arr = [1, 2, 3, 4];
let index: i32 = loop {
let count = counter as usize;
if arr.len() == count {
break -1;
}
if arr[count] == 26 {
break counter;
}
counter += 1;
};
println!("The index of 26 number is {}", index);
}
Declaring a void function
fn track_event(name: &str) {
// do something
}
fn main() {
track_event("main");
}
Returns a number (32-bit signed integer)
pub fn bigger(a: i32, b: i32) -> i32 {
if a > b {
return a;
}
b // omitting the semicolon at the end of a fn is the same as returning
}
fn main() {
let big_num = bigger(10, 20);
println!("Bigger num bwt 10 & 20 is {}", big_num);
}
Slice
fn main() {
let hello_world = String::from("Hello world");
let hello = &hello_world[0..5];
let world = &hello_world[6..11];
println!("{} {}", hello, world); // hello world
}
Declare methods on your own types
struct Number {
value: i32,
}
impl Number {
// using "&" to avoid moving the ownership of the value
fn is_odd(&self) -> bool {
self.value % 2 == 0
}
}
fn main() {
let num = Number { value: 10 };
if num.is_odd() {
println!("{} is odd", num.value);
} else {
println!("{} is even", num.value);
}
}
Declaring a block with a pair of brackets
fn main() {
let x = "out";
{
// has its own scope
let x = "in"; // this is a different `x`
println!("{}", x); // print "in"
}
println!("{}", x); // print "out"
}
Blocks are also expressions
fn main() {
let project = 26;
println!("{}", x); // print 26
}
fn main() {
let project = { 26 };
println!("{}", x); // print 26
}
They are equivalent
Anonymous functions that can capture values from the scope in which they're defined
fn main() {
let sum = |x:i32| x + 1;
println!("{}", sum(10)); // print "11"
}
fn twice<F: Fn(i32) -> i32>(x: i32, f: F) -> i32 {
f(x) + f(x)
}
fn main() {
let square = |x: i32| { x * x };
println!("{}", twice(5, square)); // prints 50
}
#![allow(dead_code, unused)]
struct Number {
odd: bool,
value: i32,
}
fn main() {
let one = Number { odd: true, value: 1 };
let two = Number { odd: false, value: 2 };
print_number(one); // Odd number: 1
print_number(two); // Even number: 2
}
fn print_number(n: Number) {
match n.odd {
true => println!("Odd number: {}", n.value),
false => println!("Even number: {}", n.value),
}
}
A match has to be exhaustive: at least one arm needs to match.
fn main() {
let one = Number { odd: true, value: 1 };
let two = Number { odd: false, value: 2 };
let three = Number { odd: false, value: 3 };
print_number(one);
print_number(two);
print_number(three);
}
fn print_number(n: Number) {
match n {
Number { value: 1, .. } => println!("One"),
Number { value: 2, .. } => println!("Two"),
Number { value, .. } => println!("{}", value),
// if that last arm didn't exist, we would get a compile-time error
}
}
fn print_number(n: Number) {
match n.value {
1 => println!("One"),
2 => println!("Two"),
_ => println!("{}", n.value), // "_" can be used as a "catch-all" pattern
}
}
trait Signed {
fn is_strictly_negative(self) -> bool;
}
Abstract definition of shared behavior with types
Orphan rules:
- one of your traits on anyone's type.
- anyone's trait on one of your types.
- but not a foreign trait on a foreign type
impl Signed for Number {
fn is_strictly_negative(self) -> bool {
self.value < 0
}
}
fn main() {
let n = Number { odd: false, value: -44 };
println!("{}", n.is_strictly_negative()); // prints "true"
}
NOTE:
You can't use "n" after printing the value (Ownership)
Your trait on a foreign type
impl Signed for i32 {
fn is_strictly_negative(self) -> bool {
self < 0
}
}
fn main() {
let n: i32 = -44; // primitive type
println!("{}", n.is_strictly_negative()); // prints "true"
}
Some traits are markers - they don't say that a type implements some methods, they say that certain things can be done with a type.
i32 implements trait Copy
Foreign trait on your type
#![allow(dead_code, unused)]
struct Number {
value: i32,
is_positive: bool,
}
// An "impl" block is always for a type
impl std::ops::Neg for Number {
type Output = Self; // Self means the current type (Number)
fn neg(self) -> Self {
let new_value = -self.value;
let is_positive = new_value > 0;
Self {
value: new_value,
is_positive: is_positive,
}
}
}
fn main() {
let n = Number { is_positive: true, value: 1 };
let m = -n; // this is only possible because we implemented `Neg`
println!("{}", m.is_positive); // prints "false"
}
// avoid warnings about unused values
let _x = 26;
// call a function but throws away its result
let _ = get_something();
// Re-binding variables
let x = 5;
let x = x + 5; // now x is equal to 10
// Destructuring a Tuple ignoring the first field
let (_, right) = slice.split_at(middle);
// Semi-colon marks the end of a statement
let x = vec![1, 2, 3, 4, 5, 6, 7, 8]
.iter()
.map(|x| x + 3)
.fold(0, |x, y| x + y);
// Multiple statements inside a block
let x = {
let y = 1; // first statement
let z = 2; // second statement
y + z // return the result of the operation
};
// Dots are used to access fields of a value
let user = get_some_struct();
user.username; // this is "jdnichollsc"
// Call a method of a value
let username = "jdnichollsc";
username.len(); // this is 11
// Use double-colon "::" to call external functions
let least = std::cmp::min(3, 8);
// std is a library (crate)
// cmp is a module (source file)
// min is a function
// Import names from other namespaces
use std::cmp::min;
use std::cmp::max;
let min_value = min(7, 1);
let max_value = max(7, 1);
// Use "globs" to import multiple names
use std::cmp::{min, max};
// Use a "wilcard" to import every symbol from a namespace
use std::cmp::*;
// Call methods as regular functions using types (namespaces)
let x = str::len("baxus"); // this is 5
// Rust inserts some non-primitive types at the beginning of every module
let v = Vec::new(); // The full path is std::vec::Vec
// Declaring a struct using double precision values
struct Vec2 {
x: f64,
y: f64
}
// Initializing a struct from another struct
let v3 = Vec2 {
x: 14.0,
..v2 // "struct update" can only happen in last position (without comma)
};
// Destructuring a struct
let v = Vec2 { x: 3.0, y: 6.0 };
let Vec2 { x, .. } = v; // ignore certain values
println!("X is {}", x); // X is 3
- Hardware with limited resources (Energy, Memory, Space, etc)
- Limited connection between devices.
Regulators
- Stable APIs (Qualified toolchain for Rust)
Architectures
- ARM Cortex-A/M
- RISC-V
- XTensa
It includes header files, build systems (Yocto, CMake), Chip/Manufacturer specific IDEs
- SVD2Rust
- PALs
- HALs
- Board Crates
- Peripheral Crates
- RTOS; RTIC, Tock, Hubris
- Async; Embassy
Memory Allocation
fn main() {
let x = String::from("hi");
let y = x; // <-- value moved here
println!("{}", x); // Error
}
fn main() {
let y = {
let x = String::from("hi");
&x
// ^^ does not live long enough
};
println!("{}", y);
}
- Take your first steps with Rust by Microsoft.
- "A half-hour to learn Rust" by amos.
- Comprehensive Rust 🦀 by Google.
- Matthew Perez - Embedded Systems Primer with Rust
By J.D Nicholls
A brief introduction about Rust
Founding Full-Stack Web3 Engineer at @baxusco | Digital nomad 🎒 | Mentor 👨🏫 | Speaker 🗣️ | Developer 👨💻 | Creator of @proyecto26 #opensource #developer