Workshop 1: Rust Fundamentals in Practice
Goal
Apply Rust ownership, error handling, and async patterns by tracing real ICN code and writing small exercises.
Prerequisites
- Completed Module 1 reading
- ICN repo cloned and buildable
Estimated time
2-3 hours
Part 1: Error Propagation Walkthrough
Steps
- Open
icn/bins/icnd/src/main.rs - Find the
main()function and locate all uses of? - For each
?, identify:- What type is being propagated?
- Where does the error eventually get handled?
Expected findings
You should find ? used for:
- Config loading (
Config::from_file) - Keystore operations
- Runtime initialization
Checkpoint
- You can trace an error from config loading to process exit
- You understand how
anyhow::Contextadds diagnostic info
Part 2: Ownership Pattern Analysis
Steps
- Open
icn/crates/icn-core/src/supervisor/mod.rs - Find the
Supervisorstruct definition - List all fields that use
Arc<...> - For each
Arcfield, explain:- Why is shared ownership needed?
- What synchronization primitive is used (
RwLock,Mutex, or none)?
Code to examine
Look for patterns like:
pub struct Supervisor {
config: Config,
trust_graph: Arc<RwLock<TrustGraph>>,
gossip_handle: Arc<RwLock<GossipActor>>,
// ...
}
Checkpoint
- You can identify at least 3
Arc-wrapped fields - You can explain why the trust graph needs
Arc<RwLock<...>>
Part 3: Async Task Spawning
Steps
- Open
icn/crates/icn-gossip/src/gossip.rs - Search for
tokio::spawn - For each spawn, identify:
- What task is being created?
- Why is it spawned instead of awaited directly?
Expected findings
Spawned tasks typically handle:
- Background timers (anti-entropy intervals)
- IO operations that shouldn't block the main loop
- Parallel message processing
Checkpoint
- You found at least one
tokio::spawnusage - You can explain the difference between
spawnand directawait
Part 4: Hands-on Exercise
Exercise A: Write a function with Result
Create a new file scratch/ownership_exercise.rs and implement:
use anyhow::{Context, Result};
/// Load a config value from environment or return a default
fn get_config_value(key: &str, default: &str) -> Result<String> {
// EXERCISE: Implement this function (intentional TODO for learners)
// Hints:
// - Use std::env::var and handle the error
// - If the var is missing, return the default
// - If the var exists but is empty, return an error
todo!("Implement this function")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_missing_var_uses_default() {
std::env::remove_var("TEST_MISSING_VAR");
let result = get_config_value("TEST_MISSING_VAR", "fallback");
assert_eq!(result.unwrap(), "fallback");
}
#[test]
fn test_present_var_is_returned() {
std::env::set_var("TEST_PRESENT_VAR", "custom_value");
let result = get_config_value("TEST_PRESENT_VAR", "fallback");
assert_eq!(result.unwrap(), "custom_value");
}
}
Exercise B: Shared ownership mini-task
Explain in writing (2-3 sentences) why the GossipActor is wrapped in
Arc<RwLock<...>> rather than just RwLock<...>.
Troubleshooting
"Cannot find struct" errors
Make sure you're looking at the correct file path. The supervisor module
was refactored; check icn/crates/icn-core/src/supervisor/mod.rs.
"Borrow checker errors" in exercises
Review the ownership diagram in Module 1. Remember:
&Tis an immutable borrow&mut Tis a mutable borrow- Only one mutable borrow OR multiple immutable borrows at a time
Summary
After completing this workshop you should be able to:
- Trace error propagation through
?in async code - Identify and explain shared ownership patterns with
Arc - Understand when to use
tokio::spawnvs direct await - Write functions that return
Resultwith proper error context
Next steps
Proceed to Module 2: Architecture Overview