Introduction to Rust

February 29, 2024 ยท View on GitHub


Workshop Overview

This workshop will primarily focus on introducing those new to Substrate/Polkadot SDK to Rust (but perhaps not new to programming). It aims to cover the necessary material to introduce Rust tooling, the concepts used in Substrate, and having them run their first lines of Rust code locally on their machine.

Note: if you want to copy-paste this into a hackmd, I recommend copying from here below, not from the very beginning!


Learning Goals

  • Install Rust and its tooling
  • Learn the basics of Rust, along with how certain features are useful for blockchain development.
  • Learn why Rust is important as a programming language, especially in the context of blockchains.
  • Learn how to write some basic Rust
  • Go over the importance of generic programming, and what it can do

Workshop Objectives

  • By the end of this workshop, you should:
    • Have Rust, cargo, and its associated tooling installed
    • Learn overarching Rust concepts needed to start Substrate dev
    • Run your first lines of Rust code, and be aware of some of its foundational concepts
    • Be exposed to an actual pallet environment

Install Rust


Clone the workshop

git clone git@github.com:CrackTheCode016/intro-to-rust-workshop.git

Run Hello World!

cargo run --bin scratchpad
   Compiling scratchpad v0.1.0 (/Users/bader/Documents/ethdenver/webzero-intro-rust/scratchpad)
    Finished dev [unoptimized + debuginfo] target(s) in 0.93s
     Running `target/debug/scratchpad`
Hello, world!

Cargo Cheat Sheet

  • cargo check - less than building, checks if it can compile
  • cargo run - builds and runs
  • cargo build - just builds

Rust && Polkadot History

  • Rust Launch - 2015
  • Polkadot whitepaper in 2016 (genesis block in 2020)
  • Polkadot writtten in Rust
  • So is Substrate, Cumulus... most things.

Polkadot's Contributions

  • Wasmi - WebAssembly interpreter
  • Polkavm - RISC-V based virtual machine
  • SCALE-codec - serialization format
  • Contributions to other libraries - libp2p, etc

Why Rust for blockchains?

  • TLDR - Safe and fast code.
  • Easily compile to WebAssembly (and other targets)
  • A good option for sensitive applications
  • Strict and extensible type system (static & strongly typed)
  • The Rust compiler yells at you.. a lot (in a good way!)

Also :p

image


Rust yelling at you: Exhibit 1

  • Ownership in Rust - avoiding common mem managment
fn take_my_value(str: String) {
    println!("{str}");
}

fn main() {
   let my_name = String::from("Bader");
   take_my_value(my_name);
   println!("{my_name}");
}

Rust yelling at you: Exhibit 1

error[E0382]: borrow of moved value: `my_name`
 --> src/main.rs:9:14
  |
7 |    let my_name = String::from("Bader");
  |        ------- move occurs because `my_name` has type `String`, which does not implement the `Copy` trait
8 |    take_my_value(my_name);
  |                  ------- value moved here
9 |    println!("{my_name}");
  |              ^^^^^^^^^ value borrowed here after move
  ...

Rust is also versatile!

  • Rust can compile to WebAssembly, allowing interop between languages
  • Once you know it, it's easy to prototype with
  • Bottomline: protects against human error
  • Shameless shill: badery.co built with Rust (wasm-based blog)

In Summary:

GHNuE2kWoAIvA9o https://twitter.com/shuttle_dev


Rust 101 - Speedrun

We will quickly go over some Rust concepts.

Note that this is just what's important for us right now, there is obviously a lot more in Rust


Rust 101 - The 101

  • Strongly and statically typed
  • Focuses on memory safety
  • Combines concepts of object-oriented and functional programming
  • Powerful memory managment system w/o a garbage collector

Rust 101 - Terminology

  • A Rust project / package is called a crate
  • A workspace is multiple crates as a single project
  • Cargo is our build tool

Rust 101 - Variables

  • Immutable by default, must explicitly declare mutability
let age: u32 = 22;
โŒ age += 1;
// A mutable variable (also, note shadowing)
let mut age: u64 = 22;
โœ… age += 1;

Rust 101 - Collections

  • Vec = Growable arrays (handy macro)
  • HashMap = Sets/Dictionary (std::collections::HashMap)
  • String = a &str you can modify
let vector = vec![1, 2, 3];
// or
let mut vector: Vec<u32> = Vec::new();
vector.push(1);

let mut hash_map: HashMap<u32, u32> = HashMap::new();
hash_map.insert(1, 50);

let string = String::from("Hey!");

Rust 101 - Functions

// No parameters or return type
fn a_function() {}

// Just parameters
fn print_age(age: u32) {
    // (btw, this is how you print stuff)
    println!("{age}");
}

// Both (note return syntax)
fn increase_age(age: u32) -> u32 {
    age + 1
}

Rust 101 - Structs

struct Person {
    name: String,
    age: u32
}

Rust 101 - Extending Structs

  • One is more like a constructor
  • Using &self requires the struct to be instantiated first
impl Person {
    fn new(name: &str, age: u32) -> Self {
        Person { String::from(name), age }
    }
    
    fn say_name(&self) {
        println!("yo my name is {}", self.name);
    }
}

// .. later

Person::new("Bader", 22);
Person.say_name();

Rust 101 - Traits

  • Define common behavior for an object
trait Sound {
    fn make_sound(&self);
}

impl Sound for Person {
    fn make_sound(&self) {
        println!("hello, {}", self.name)
    }
}

// .. later
person.make_sound(); // "hello, name"

Rust 101 - Common Traits + Derive

  • Common traits to print, clone, etc
  • #[derive] macro used to avoid boilerplate
#[derive(Clone, Debug)]
struct Person {
    name: String,
    age: u32
}
// ..
println!("{:?}", person);
// .. or
let new_person = person.clone();

Rust 101 - Generics

  • Placeholder types
  • Calculator<u32>
  • Calculator<i32>
  • Calculator<String>
struct Calculator<T> {
    // T is just a placeholder 
    // for a type we put in the future
    result: T
}

Rust 101 - Associated Types

  • Another type of generic
  • Can be used to configure traits
trait Factory {
    // Product is an associated type
    type Product: Clone + Debug;
    fn make_product() -> Self::Product;
}
// .. later:

type Product = Table;

What's the diff?

  • Associated Types - Use when you only need to implement a trait once.
pub struct EvmRuntime;

impl RuntimeConfig for EvmRuntime {
    type Balance = u128;
    type AccountId = [u8; 64];
    type ExecutableCode = String;
}

What's the diff?

  • Generics - Use for when you need to implement a trait for multiple types.
// 32 Bit Signed Integer
let squared_i32 = square::<i32>(10);
// 32 Bit Unsigned Integer
let squared_u32 = square::<u32>(10);
// Floating Point Number
let squared_f32 = square::<f32>(10.0);

Rust 101 - Trait Bounds

  • Bound placeholder types (generics) with traits
  • In this case, we could pass in both Dog or a Person and it doesn't matter
fn make_lots_of_noise<T: Sound>(thing: T, thing2: T) {
    //.. other stuff
    thing.make_sound();
    //.. more stuff
}
  • Generics & associated types being used ๐Ÿ˜ฒ
// Output is an associated type of Mul
fn square<T: Mul<Output = T> + Copy>(x: T) -> T {
    x * x
}

Rust 101 - Trait Bounds

  • Restrict it only to numbers that "can count"
struct Calculator<T: CanCount> {
    result: T
}

Rust 101 - Macros

  • Code the generates code
  • Elimiates boilerplate
println!();
// or...

::std::io::_print(format_args!("\n"));

macro_rules! println {
    () => { ... };
    ($($arg:tt)*) => { ... };
}

Rust 101 - Expanding Macros

  • If you installed cargo-expand..
  • cargo expand in any crate that has macros will give you the output of the macro

Generic Programming

  • Now, to put it together
  • Generic Programming: Make assumptions about behavior, but not specific implemention
  • Generic traits - creating reusable APIs

Example: Polkadot!

  • Doesn't make assumptions about what you implement
  • Uses traits and generics to tell you how to implement. (Blocks, extrinsics, fork choice rules..)
  • Traits used as a guide for how to implement common components (i.e., a block, a transaction, a pallet)

Example: Pallets

  • Pallets are building blocks for your blockchain
  • Each pallet's configuration is a trait, called Config, which has associated types.
  • The runtime itself has a configuration trait

Example: Pallets

/// Configure the pallet by specifying the parameters and types on which it depends.
#[pallet::config]
pub trait Config: frame_system::Config {
    /// The max amount of messages per user per conversation
    #[pallet::constant]
    type MaxMessageAmount: Get<u32>;
}

Then later...

  • Later, all of these are combined into one big runtime, which is essentially the business logic of our blockchain
  • But don't worry about that right now!
impl pallet_uke::Config for Runtime {
	type MaxMessageAmount = ConstU32<16>;
}

Workshop Time: Factory Example ๐Ÿญ

  • Our goal: provide a way to create factories for multiple processes of the same kind
  • Raw Materials -> Processes -> Products
  • Example: IronOre -> Process It -> Steel

Open scratchpad/src/factory.rs

Later, we'll import this as a module inside of main.rs


Factory Struct

  • The generic P process
  • We can access associated types from our trait!
  • This is only one type of process per factory (homogenous factory)
pub struct Factory<P: Process> {
    factory_id: u32,
    processes: Vec<P>,
    completed_products: HashMap<usize, P::Product>,
}

Factory Methods

impl<P: Process> Factory<P> {
// Our "constructor"
pub fn new(factory_id: u32) -> Self {
    Factory { factory_id, processes: vec![], completed_products: HashMap::new() }
}

Factory Methods

pub fn process_all(&mut self) {
    // loop thru and run "push_along_belt", then push the products to completed products.
    for (id, process) in self.processes.iter().enumerate() {
        println!("Processing {}...", id);
        let product: <P as Process>::Product = process.push_along_the_belt(process.material());
        self.completed_products.insert(id, product.clone());
        println!("Process successful, product produced! {:?}...", product);
    }
}

Factory Methods

    pub fn add_new_process(&mut self, process: P) {
        self.processes.push(process)
    }

    // Run the factory
    pub fn run(&mut self) {
        // run all belts
        self.process_all();
    }
}

Process Trait

  • Remember: we want to define how a process works, not just a process itself
  • A blueprint for infinitely many processes!
  • Note: we can also use trait bounds on associated types.

Process Trait

trait Process {
    // Note the use of associated functions as a means to "configure" our belt
    type RawMaterial;
    type Product: Debug + Clone;
    
    /// Processes the item from a raw material to a product.
    fn push_along_the_belt(&self, material: Self::RawMaterial) -> Self::Product;

    // The raw material to process on this belt
    fn material(&self) -> Self::RawMaterial;
}

Raw Material: IronOre

pub struct IronOre {
    pub amount: u32
}

Product: Steel

#[derive(Debug, Clone)]
pub struct Steel {
    pub amount: u32,
}

Process

pub struct OreProcess {
    pub amount: u32
}

Implementing Our Process

impl Process for OreProcess {
    type RawMaterial = IronOre;
    type Product = Steel;

    fn push_along_the_belt(&self, material: Self::RawMaterial) -> Self::Product {
        Steel { amount: material.amount / 2 }
    }

    fn material(&self) -> Self::RawMaterial {
        IronOre { amount: self.amount }
    }
}

Running our Factory

  • Go to scrachpad/src/main.rs and replace the main function with:
/// Do factory stuff.
fn main() {
    let ore_process = OreProcess { amount: 20 };
    let mut metal_factory: Factory<OreProcess> = Factory::new(0);
    metal_factory.add_new_process(ore_process);
    metal_factory.run();
}

Summary

  • We defined how a factory process operates via a trait
  • We successfully created a new process, and ran our factory!
  • Lots of innovation can happen (multithreading for process, multi-process Factory, a Factory trait...)

A Look at a Substrate Pallet

  • Usually this is with the node-template, but this specific repo is just the pallet on its own

https://github.com/Uke-Messaging/uke-pallet


Configuring Traits


Questions


Where to go next


Feedback!