Rust 101 Practical Examples

Section 1-2: Variables & Types

Immutable by default - THE mental shift
fn main() {
    let ip_address = "10.50.1.50";      // immutable - can't change
    let mut connection_count = 0;        // mut = mutable

    connection_count += 1;               // OK - it's mutable
    // ip_address = "10.50.1.51";        // ERROR - not mutable

    println!("DC at {} has {} connections", ip_address, connection_count);
}
Shadowing - same name, new binding
fn main() {
    let vlan = "10";                     // String slice
    let vlan: u16 = vlan.parse().unwrap(); // Shadow with new type
    let vlan = vlan + 100;               // Shadow again: 110

    println!("VLAN: {}", vlan);
}
Type annotations - when Rust can’t infer
fn main() {
    let port: u16 = 443;                 // Explicit type
    let hosts: Vec<&str> = vec!["kvm-01", "kvm-02", "kvm-03"];
    let active: bool = true;

    // Rust infers these:
    let timeout = 30;                    // i32 by default
    let ratio = 0.75;                    // f64 by default
}

Section 3: Functions

Function with return value
fn is_private_ip(ip: &str) -> bool {
    ip.starts_with("10.") ||
    ip.starts_with("192.168.") ||
    ip.starts_with("172.16.")
}

fn main() {
    let server = "10.50.1.20";
    if is_private_ip(server) {
        println!("{} is RFC1918", server);
    }
}
Multiple return values with tuple
fn parse_endpoint(endpoint: &str) -> (&str, u16) {
    let parts: Vec<&str> = endpoint.split(':').collect();
    let host = parts[0];
    let port: u16 = parts[1].parse().unwrap_or(443);
    (host, port)
}

fn main() {
    let (host, port) = parse_endpoint("ise-01.inside.domusdigitalis.dev:443");
    println!("Connecting to {} on port {}", host, port);
}

Section 4: Control Flow

Match expression - Rust’s powerful switch
fn get_service_port(service: &str) -> u16 {
    match service {
        "ssh"    => 22,
        "dns"    => 53,
        "http"   => 80,
        "https"  => 443,
        "ldap"   => 389,
        "ldaps"  => 636,
        "radius" => 1812,
        _        => 0,  // Default case (required!)
    }
}

fn main() {
    let port = get_service_port("ldaps");
    println!("LDAPS runs on port {}", port);
}
If let - when you only care about one case
fn main() {
    let config_line = Some("AuthenticationMethods publickey");

    if let Some(line) = config_line {
        println!("Found: {}", line);
    }
    // No else needed - we only care about Some
}

Section 5: Ownership & Borrowing (THE BIG ONE)

DIAGRAM THIS: Ownership moves
fn main() {
    let hostname = String::from("kvm-01");  // hostname OWNS the String

    let server = hostname;                   // Ownership MOVES to server

    // println!("{}", hostname);             // ERROR! hostname is gone
    println!("{}", server);                  // OK - server owns it now
}
STACK                    HEAP
┌─────────────┐         ┌───────────────┐
│ hostname    │────────►│ "kvm-01"      │
│ ptr, len, cap│         └───────────────┘
└─────────────┘
       │
       ▼ MOVE
┌─────────────┐         ┌───────────────┐
│ server      │────────►│ "kvm-01"      │
│ ptr, len, cap│         └───────────────┘
└─────────────┘
(hostname is now invalid)
DIAGRAM THIS: Borrowing with &
fn print_host(host: &String) {       // Borrows, doesn't take ownership
    println!("Host: {}", host);
}                                     // Borrow ends, nothing dropped

fn main() {
    let hostname = String::from("kvm-01");

    print_host(&hostname);            // Lend it out
    print_host(&hostname);            // Can lend again!

    println!("Still own: {}", hostname);  // Still valid
}
STACK                    HEAP
┌─────────────┐         ┌───────────────┐
│ hostname    │────────►│ "kvm-01"      │
│ ptr, len, cap│         └───────────────┘
└─────────────┘               ▲
       │                      │
       ▼ BORROW (&)           │
┌─────────────┐               │
│ host        │───────────────┘
│ (reference) │  (points to same data)
└─────────────┘
(hostname still valid!)
DIAGRAM THIS: Mutable borrow
fn add_domain(host: &mut String) {
    host.push_str(".inside.domusdigitalis.dev");
}

fn main() {
    let mut hostname = String::from("kvm-01");

    add_domain(&mut hostname);        // Mutable borrow

    println!("{}", hostname);         // kvm-01.inside.domusdigitalis.dev
}
The Rules (memorize these)
OWNERSHIP RULES:
1. Each value has exactly ONE owner
2. When owner goes out of scope, value is dropped
3. Ownership can be moved or borrowed

BORROWING RULES:
1. Many immutable borrows (&T) OR one mutable borrow (&mut T)
2. References must always be valid (no dangling)

Section 6: Structs

Network device struct
struct NetworkDevice {
    hostname: String,
    ip: String,
    vlan: u16,
    active: bool,
}

impl NetworkDevice {
    // Constructor (convention: new)
    fn new(hostname: &str, ip: &str, vlan: u16) -> Self {
        NetworkDevice {
            hostname: hostname.to_string(),
            ip: ip.to_string(),
            vlan,
            active: true,
        }
    }

    // Method (takes &self)
    fn fqdn(&self) -> String {
        format!("{}.inside.domusdigitalis.dev", self.hostname)
    }

    // Method that modifies (takes &mut self)
    fn disable(&mut self) {
        self.active = false;
    }
}

fn main() {
    let mut switch = NetworkDevice::new("sw-01", "10.50.1.10", 1);

    println!("FQDN: {}", switch.fqdn());

    switch.disable();
    println!("Active: {}", switch.active);
}

Section 7: Enums & Pattern Matching

Connection state enum
enum ConnectionState {
    Disconnected,
    Connecting { attempt: u8 },
    Connected { session_id: String },
    Failed { error: String },
}

fn handle_state(state: &ConnectionState) {
    match state {
        ConnectionState::Disconnected => {
            println!("Not connected");
        }
        ConnectionState::Connecting { attempt } => {
            println!("Connecting... attempt {}", attempt);
        }
        ConnectionState::Connected { session_id } => {
            println!("Connected: {}", session_id);
        }
        ConnectionState::Failed { error } => {
            println!("Failed: {}", error);
        }
    }
}

fn main() {
    let state = ConnectionState::Connected {
        session_id: "sess-abc123".to_string()
    };
    handle_state(&state);
}

Section 8: Error Handling

Result type - no exceptions in Rust
use std::fs;
use std::io;

fn read_config(path: &str) -> Result<String, io::Error> {
    fs::read_to_string(path)
}

fn main() {
    match read_config("/etc/hosts") {
        Ok(contents) => println!("Read {} bytes", contents.len()),
        Err(e) => println!("Failed to read: {}", e),
    }
}
The ? operator - propagate errors up
use std::fs;
use std::io;

fn get_hostname() -> Result<String, io::Error> {
    let contents = fs::read_to_string("/etc/hostname")?;  // ? = return Err if fails
    Ok(contents.trim().to_string())
}

fn main() -> Result<(), io::Error> {
    let hostname = get_hostname()?;
    println!("Hostname: {}", hostname);
    Ok(())
}
Option type - nullable done right
fn find_port(service: &str) -> Option<u16> {
    match service {
        "ssh"   => Some(22),
        "https" => Some(443),
        _       => None,
    }
}

fn main() {
    // Handle None explicitly
    if let Some(port) = find_port("ssh") {
        println!("SSH port: {}", port);
    }

    // Or provide default
    let port = find_port("unknown").unwrap_or(8080);
    println!("Using port: {}", port);
}

Section 9: Collections

Vec - growable array
fn main() {
    let mut servers: Vec<&str> = Vec::new();

    servers.push("kvm-01");
    servers.push("kvm-02");
    servers.push("kvm-03");

    // Iterate
    for server in &servers {
        println!("Server: {}", server);
    }

    // Access by index
    if let Some(first) = servers.get(0) {
        println!("First: {}", first);
    }

    println!("Total: {} servers", servers.len());
}
HashMap - key-value store
use std::collections::HashMap;

fn main() {
    let mut ports: HashMap<&str, u16> = HashMap::new();

    ports.insert("ssh", 22);
    ports.insert("https", 443);
    ports.insert("ldaps", 636);

    // Lookup
    if let Some(&port) = ports.get("ldaps") {
        println!("LDAPS: {}", port);
    }

    // Iterate
    for (service, port) in &ports {
        println!("{} -> {}", service, port);
    }
}

Section 10: Traits

Define shared behavior
trait Pingable {
    fn ping(&self) -> bool;
    fn address(&self) -> &str;
}

struct Server {
    hostname: String,
    ip: String,
}

struct Switch {
    name: String,
    mgmt_ip: String,
}

impl Pingable for Server {
    fn ping(&self) -> bool {
        println!("Pinging server {}", self.ip);
        true
    }
    fn address(&self) -> &str {
        &self.ip
    }
}

impl Pingable for Switch {
    fn ping(&self) -> bool {
        println!("Pinging switch {}", self.mgmt_ip);
        true
    }
    fn address(&self) -> &str {
        &self.mgmt_ip
    }
}

// Function that works on ANY Pingable
fn check_device(device: &impl Pingable) {
    println!("Checking {}", device.address());
    if device.ping() {
        println!("OK");
    }
}

fn main() {
    let srv = Server { hostname: "kvm-01".into(), ip: "10.50.1.100".into() };
    let sw = Switch { name: "sw-01".into(), mgmt_ip: "10.50.1.10".into() };

    check_device(&srv);
    check_device(&sw);
}

Diagram Templates

Use these ASCII patterns for your diagrams:

Stack vs Heap
STACK                    HEAP
┌─────────────┐         ┌───────────────┐
│ variable    │────────►│ actual data   │
│ ptr,len,cap │         │ on heap       │
└─────────────┘         └───────────────┘
Ownership Move
let a = String::from("x");
let b = a;  // MOVE

  BEFORE          AFTER
┌────┐           ┌────┐
│ a  │──►data    │ a  │ (invalid)
└────┘           └────┘
                 ┌────┐
                 │ b  │──►data
                 └────┘
Borrow
let a = String::from("x");
let b = &a;  // BORROW

┌────┐         ┌──────┐
│ a  │────────►│ data │
└────┘         └──────┘
   ▲               ▲
   │               │
┌────┐             │
│ b  │─────────────┘
└────┘  (reference)
Scope and Drop
{                          // Scope starts
    let x = String::new(); // x created
    // ... use x ...
}                          // Scope ends: x DROPPED, memory freed