# Rust Bootstrap for C# Developers A structured introduction to Rust for developers with C# experience. This guide follows a proven pedagogical approach, building concepts step by step to help you understand not just *how* Rust works, but *why* it was designed this way. ## Course Overview - **The case for Rust** - Why Rust matters for C# developers - **Getting started** - Installation, tooling, and your first program - **Basic building blocks** - Types, variables, control flow - **Data structures** - Arrays, tuples, structs - **Pattern matching and enums** - Essential Rust concepts - **Modules and crates** - Code organization and dependencies (vs .NET assemblies) - **Traits and generics** - Advanced type system - **Error handling** - Rust's approach to safety - **Memory management** - Ownership, borrowing, and lifetimes - **Practical migration** - Real-world examples ## Table of Contents ### 1. Introduction and Motivation - [Quick Reference: Rust vs C#](#quick-reference-rust-vs-c) - [The Case for Rust for C# Developers](#the-case-for-rust-for-c-developers) - [Common C# Pain Points That Rust Addresses](#common-c-pain-points-that-rust-addresses) - [When to Choose Rust Over C#](#when-to-choose-rust-over-c) ### 2. Getting Started - [Installation and Setup](#installation-and-setup) - [Your First Rust Program](#your-first-rust-program) - [Cargo vs NuGet/MSBuild](#cargo-vs-nugetmsbuild) - [IDE Setup for C# Developers](#ide-setup-for-c-developers) ### 3. Basic Types and Variables - [Built-in Types Comparison](#built-in-types-comparison) - [Variables and Mutability](#variables-and-mutability) - [String Types: String vs &str](#string-types-string-vs-str) - [Comments and Documentation](#comments-and-documentation) ### 4. Control Flow - [Conditional Statements](#conditional-statements) - [Loops and Iteration](#loops-and-iteration) - [Expression Blocks](#expression-blocks) - [Functions vs Methods](#functions-vs-methods) ### 5. Data Structures - [Arrays and Slices](#arrays-and-slices) - [Tuples](#tuples) - [Structs vs Classes](#structs-vs-classes) - [References and Borrowing Basics](#references-and-borrowing-basics) ### 6. Pattern Matching and Enums - [Enums vs C# Enums](#enums-vs-c-enums) - [Match Expressions](#match-expressions) - [Option for Null Safety](#optiont-for-null-safety) - [Result for Error Handling](#resultt-e-for-error-handling) ### 7. Modules and Crates - [Rust Modules vs C# Namespaces](#rust-modules-vs-c-namespaces) - [Crates vs .NET Assemblies](#crates-vs-net-assemblies) - [Package Management: Cargo vs NuGet](#package-management-cargo-vs-nuget) - [Visibility and Access Control](#visibility-and-access-control) ### 8. Traits and Generics - [Traits vs Interfaces](#traits-vs-interfaces) - [Generic Types and Functions](#generic-types-and-functions) - [Trait Bounds and Constraints](#trait-bounds-and-constraints) - [Common Standard Library Traits](#common-standard-library-traits) ### 9. Collections and Error Handling - [Vec vs List](#vect-vs-listt) - [HashMap vs Dictionary](#hashmap-vs-dictionary) - [Iterator Patterns](#iterator-patterns) - [Comprehensive Error Handling](#comprehensive-error-handling) ### 10. Memory Management - [Understanding Ownership](#understanding-ownership) - [Move Semantics vs Reference Semantics](#move-semantics-vs-reference-semantics) - [Borrowing and Lifetimes](#borrowing-and-lifetimes) - [Smart Pointers](#smart-pointers) ### 11. Practical Migration Examples - [Configuration Management](#configuration-management) - [Data Processing Pipelines](#data-processing-pipelines) - [HTTP Clients and APIs](#http-clients-and-apis) - [File I/O and Serialization](#file-io-and-serialization) ### 12. Next Steps and Best Practices - [Testing in Rust vs C#](#testing-in-rust-vs-c) - [Common Pitfalls for C# Developers](#common-pitfalls-for-c-developers) - [Learning Path and Resources](#learning-path-and-resources) - [Moving to Advanced Topics](#moving-to-advanced-topics) *** ## Quick Reference: Rust vs C# | **Concept** | **C#** | **Rust** | **Key Difference** | |-------------|--------|----------|-------------------| | Memory management | Garbage collector | Ownership system | Zero-cost, deterministic cleanup | | Null references | `null` everywhere | `Option` | Compile-time null safety | | Error handling | Exceptions | `Result` | Explicit, no hidden control flow | | Mutability | Mutable by default | Immutable by default | Opt-in to mutation | | Type system | Reference/value types | Ownership types | Move semantics, borrowing | | Assemblies | GAC, app domains (.NET Framework); side-by-side (.NET 5+) | Crates | Static linking, no runtime | | Namespaces | `using System.IO` | `use std::fs` | Module system | | Interfaces | `interface IFoo` | `trait Foo` | Default implementations | | Generics | `List` (optional constraints via `where`) | `Vec` (trait bounds like `T: Clone`) | Zero-cost abstractions | | Threading | locks, async/await | Ownership + Send/Sync | Data race prevention | | Performance | JIT compilation | AOT compilation | Predictable, no GC pauses | *** ## The Case for Rust for C# Developers ### Performance Without the Runtime Tax ```csharp // C# - Great productivity, runtime overhead public class DataProcessor { private List data = new List(); public void ProcessLargeDataset() { // Allocations trigger GC for (int i = 0; i < 10_000_000; i++) { data.Add(i * 2); // GC pressure } // Unpredictable GC pauses during processing } } // Runtime: Variable (50-200ms due to GC) // Memory: ~80MB (including GC overhead) // Predictability: Low (GC pauses) ``` ```rust // Rust - Same expressiveness, zero runtime overhead struct DataProcessor { data: Vec, } impl DataProcessor { fn process_large_dataset(&mut self) { // Zero-cost abstractions for i in 0..10_000_000 { self.data.push(i * 2); // No GC pressure } // Deterministic performance } } // Runtime: Consistent (~30ms) // Memory: ~40MB (exact allocation) // Predictability: High (no GC) ``` ### Memory Safety Without Runtime Checks ```csharp // C# - Runtime safety with overhead public class UnsafeOperations { public string ProcessArray(int[] array) { // Runtime bounds checking if (array.Length > 0) { return array[0].ToString(); // NullReferenceException possible } return null; // Null propagation } public void ProcessConcurrently() { var list = new List(); // Data races possible, requires careful locking Parallel.For(0, 1000, i => { lock (list) // Runtime overhead { list.Add(i); } }); } } ``` ```rust // Rust - Compile-time safety with zero runtime cost struct SafeOperations; impl SafeOperations { // Compile-time null safety, no runtime checks fn process_array(array: &[i32]) -> Option { array.first().map(|x| x.to_string()) // No null references possible // Bounds checking optimized away when provably safe } fn process_concurrently() { use std::sync::Mutex; use std::thread; let data = Mutex::new(Vec::new()); // Data races prevented at compile time let handles: Vec<_> = (0..1000).map(|i| { let data = &data; thread::spawn(move || { data.lock().unwrap().push(i); // No lock overhead when single-threaded }) }).collect(); for handle in handles { handle.join().unwrap(); } } } ``` *** ## Common C# Pain Points That Rust Addresses ### 1. The Billion Dollar Mistake: Null References ```csharp // C# - Null reference exceptions are runtime bombs public class UserService { public string GetUserDisplayName(User user) { // Any of these could throw NullReferenceException return user.Profile.DisplayName.ToUpper(); // ^^^^^ ^^^^^^^ ^^^^^^^^^^^ ^^^^^^^ // Could be null at runtime } // Nullable reference types (C# 8+) help, but nulls can still slip through public string GetDisplayName(User? user) { return user?.Profile?.DisplayName?.ToUpper() ?? "Unknown"; // This specific line is null-safe thanks to ?. and ??, // but NRTs are advisory β€” the compiler can be overridden with `!` } } ``` ```rust // Rust - Null safety guaranteed at compile time struct UserService; impl UserService { fn get_user_display_name(user: &User) -> Option { user.profile.as_ref()? .display_name.as_ref() .map(|name| name.to_uppercase()) // Compiler forces you to handle None case // Impossible to have null pointer exceptions } fn get_display_name_safe(user: Option<&User>) -> String { user.and_then(|u| u.profile.as_ref()) .and_then(|p| p.display_name.as_ref()) .map(|name| name.to_uppercase()) .unwrap_or_else(|| "Unknown".to_string()) // Explicit handling, no surprises } } ``` ### 2. Hidden Exceptions and Control Flow ```csharp // C# - Exceptions can be thrown from anywhere public async Task GetUserDataAsync(int userId) { // Each of these might throw different exceptions var user = await userRepository.GetAsync(userId); // SqlException var permissions = await permissionService.GetAsync(user); // HttpRequestException var preferences = await preferenceService.GetAsync(user); // TimeoutException return new UserData(user, permissions, preferences); // Caller has no idea what exceptions to expect } ``` ```rust // Rust - All errors explicit in function signatures #[derive(Debug)] enum UserDataError { DatabaseError(String), NetworkError(String), Timeout, UserNotFound(i32), } async fn get_user_data(user_id: i32) -> Result { // All errors explicit and handled let user = user_repository.get(user_id).await .map_err(UserDataError::DatabaseError)?; let permissions = permission_service.get(&user).await .map_err(UserDataError::NetworkError)?; let preferences = preference_service.get(&user).await .map_err(|_| UserDataError::Timeout)?; Ok(UserData::new(user, permissions, preferences)) // Caller knows exactly what errors are possible } ``` ### 3. Unpredictable Performance Due to GC ```csharp // C# - GC can pause at any time public class HighFrequencyTrader { private List trades = new List(); public void ProcessMarketData(MarketTick tick) { // Allocations can trigger GC at worst possible moment var analysis = new MarketAnalysis(tick); trades.Add(new Trade(analysis.Signal, tick.Price)); // GC might pause here during critical market moment // Pause duration: 1-100ms depending on heap size } } ``` ```rust // Rust - Predictable, deterministic performance struct HighFrequencyTrader { trades: Vec, } impl HighFrequencyTrader { fn process_market_data(&mut self, tick: MarketTick) { // Zero allocations, predictable performance let analysis = MarketAnalysis::from(tick); self.trades.push(Trade::new(analysis.signal(), tick.price)); // No GC pauses, consistent sub-microsecond latency // Performance guaranteed by type system } } ``` *** ## When to Choose Rust Over C# ### βœ… Choose Rust When: - **Performance is critical**: Real-time systems, high-frequency trading, game engines - **Memory usage matters**: Embedded systems, cloud costs, mobile applications - **Predictability required**: Medical devices, automotive, financial systems - **Security is paramount**: Cryptography, network security, system-level code - **Long-running services**: Where GC pauses cause issues - **Resource-constrained environments**: IoT, edge computing - **System programming**: CLI tools, databases, web servers, operating systems ### βœ… Stay with C# When: - **Rapid application development**: Business applications, CRUD applications - **Large existing codebase**: When migration cost is prohibitive - **Team expertise**: When Rust learning curve doesn't justify benefits - **Enterprise integrations**: Heavy .NET Framework/Windows dependencies - **GUI applications**: WPF, WinUI, Blazor ecosystems - **Time to market**: When development speed trumps performance ### πŸ”„ Consider Both (Hybrid Approach): - **Performance-critical components in Rust**: Called from C# via P/Invoke - **Business logic in C#**: Familiar, productive development - **Gradual migration**: Start with new services in Rust *** ## Real-World Impact: Why Companies Choose Rust ### Dropbox: Storage Infrastructure - **Before (Python)**: High CPU usage, memory overhead - **After (Rust)**: 10x performance improvement, 50% memory reduction - **Result**: Millions saved in infrastructure costs ### Discord: Voice/Video Backend - **Before (Go)**: GC pauses causing audio drops - **After (Rust)**: Consistent low-latency performance - **Result**: Better user experience, reduced server costs ### Microsoft: Windows Components - **Rust in Windows**: File system, networking stack components - **Benefit**: Memory safety without performance cost - **Impact**: Fewer security vulnerabilities, same performance ### Why This Matters for C# Developers: 1. **Complementary skills**: Rust and C# solve different problems 2. **Career growth**: Systems programming expertise increasingly valuable 3. **Performance understanding**: Learn zero-cost abstractions 4. **Safety mindset**: Apply ownership thinking to any language 5. **Cloud costs**: Performance directly impacts infrastructure spend *** ## Installation and Setup ### Installing Rust ```bash # Install Rust (works on Windows, macOS, Linux) curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # On Windows, you can also download from: https://rustup.rs/ ``` ### Rust Tools vs C# Tools | C# Tool | Rust Equivalent | Purpose | |---------|----------------|---------| | `dotnet new` | `cargo new` | Create new project | | `dotnet build` | `cargo build` | Compile project | | `dotnet run` | `cargo run` | Run project | | `dotnet test` | `cargo test` | Run tests | | NuGet | Crates.io | Package repository | | MSBuild | Cargo | Build system | | Visual Studio | VS Code + rust-analyzer | IDE | ### IDE Setup 1. **VS Code** (Recommended for beginners) - Install "rust-analyzer" extension - Install "CodeLLDB" for debugging 2. **Visual Studio** (Windows) - Install Rust support extension 3. **JetBrains RustRover** (Full IDE) - Similar to Rider for C# *** ## Your First Rust Program ### C# Hello World ```csharp // Program.cs using System; namespace HelloWorld { class Program { static void Main(string[] args) { Console.WriteLine("Hello, World!"); } } } ``` ### Rust Hello World ```rust // main.rs fn main() { println!("Hello, World!"); } ``` ### Key Differences for C# Developers 1. **No classes required** - Functions can exist at the top level 2. **No namespaces** - Uses module system instead 3. **`println!` is a macro** - Notice the `!` 4. **Semicolons matter** - Omitting the trailing semicolon turns a statement into a return expression 5. **No explicit return type** - `main` returns `()` (unit type) ### Creating Your First Project ```bash # Create new project (like 'dotnet new console') cargo new hello_rust cd hello_rust # Project structure created: # hello_rust/ # β”œβ”€β”€ Cargo.toml (like .csproj file) # └── src/ # └── main.rs (like Program.cs) # Run the project (like 'dotnet run') cargo run ``` *** ## Cargo vs NuGet/MSBuild ### Project Configuration **C# (.csproj)** ```xml Exe net8.0 ``` **Rust (Cargo.toml)** ```toml [package] name = "hello_rust" version = "0.1.0" edition = "2021" [dependencies] serde_json = "1.0" # Like Newtonsoft.Json log = "0.4" # Like Serilog ``` ### Common Cargo Commands ```bash # Create new project cargo new my_project cargo new my_project --lib # Create library project # Build and run cargo build # Like 'dotnet build' cargo run # Like 'dotnet run' cargo test # Like 'dotnet test' # Package management cargo add serde # Add dependency (like 'dotnet add package') cargo update # Update dependencies # Release build cargo build --release # Optimized build cargo run --release # Run optimized version # Documentation cargo doc --open # Generate and open docs ``` ### Workspace vs Solution **C# Solution (.sln)** ``` MySolution/ β”œβ”€β”€ MySolution.sln β”œβ”€β”€ WebApi/ β”‚ └── WebApi.csproj β”œβ”€β”€ Business/ β”‚ └── Business.csproj └── Tests/ └── Tests.csproj ``` **Rust Workspace (Cargo.toml)** ```toml [workspace] members = [ "web_api", "business", "tests" ] ``` *** ## Variables and Mutability ### C# Variable Declaration ```csharp // C# - Variables are mutable by default int count = 0; // Mutable count = 5; // βœ… Works readonly int maxSize = 100; // Immutable after initialization // maxSize = 200; // ❌ Compile error const int BUFFER_SIZE = 1024; // Compile-time constant ``` ### Rust Variable Declaration ```rust // Rust - Variables are immutable by default let count = 0; // Immutable by default // count = 5; // ❌ Compile error: cannot assign twice to immutable variable let mut count = 0; // Explicitly mutable count = 5; // βœ… Works const BUFFER_SIZE: usize = 1024; // Compile-time constant ``` ### Key Mental Shift for C# Developers ```rust // Think of 'let' as 'readonly' by default let name = "John"; // Like: readonly string name = "John"; let mut age = 30; // Like: int age = 30; // Variable shadowing (unique to Rust) let spaces = " "; // String let spaces = spaces.len(); // Now it's a number (usize) // This is different from mutation - we're creating a new variable ``` ### Practical Example: Counter ```csharp // C# version public class Counter { private int value = 0; public void Increment() { value++; // Mutation } public int GetValue() => value; } ``` ```rust // Rust version pub struct Counter { value: i32, // Private by default } impl Counter { pub fn new() -> Counter { Counter { value: 0 } } pub fn increment(&mut self) { // &mut needed for mutation self.value += 1; } pub fn get_value(&self) -> i32 { self.value } } ``` *** ## Data Types Comparison ### Primitive Types | C# Type | Rust Type | Size | Range | |---------|-----------|------|-------| | `byte` | `u8` | 8 bits | 0 to 255 | | `sbyte` | `i8` | 8 bits | -128 to 127 | | `short` | `i16` | 16 bits | -32,768 to 32,767 | | `ushort` | `u16` | 16 bits | 0 to 65,535 | | `int` | `i32` | 32 bits | -2Β³ΒΉ to 2Β³ΒΉ-1 | | `uint` | `u32` | 32 bits | 0 to 2Β³Β²-1 | | `long` | `i64` | 64 bits | -2⁢³ to 2⁢³-1 | | `ulong` | `u64` | 64 bits | 0 to 2⁢⁴-1 | | `float` | `f32` | 32 bits | IEEE 754 | | `double` | `f64` | 64 bits | IEEE 754 | | `bool` | `bool` | 1 bit | true/false | | `char` | `char` | 32 bits | Unicode scalar | ### Size Types (Important!) ```csharp // C# - int is always 32-bit int arrayIndex = 0; long fileSize = file.Length; ``` ```rust // Rust - size types match pointer size (32-bit or 64-bit) let array_index: usize = 0; // Like size_t in C let file_size: u64 = file.len(); // Explicit 64-bit ``` ### Type Inference ```csharp // C# - var keyword var name = "John"; // string var count = 42; // int var price = 29.99; // double ``` ```rust // Rust - automatic type inference let name = "John"; // &str (string slice) let count = 42; // i32 (default integer) let price = 29.99; // f64 (default float) // Explicit type annotations let count: u32 = 42; let price: f32 = 29.99; ``` ### Arrays and Collections Overview ```csharp // C# - reference types, heap allocated int[] numbers = new int[5]; // Fixed size List list = new List(); // Dynamic size ``` ```rust // Rust - multiple options let numbers: [i32; 5] = [1, 2, 3, 4, 5]; // Stack array, fixed size let mut list: Vec = Vec::new(); // Heap vector, dynamic size ``` *** ## String Types: String vs &str This is one of the most confusing concepts for C# developers, so let's break it down carefully. ### C# String Handling ```csharp // C# - Simple string model string name = "John"; // String literal string greeting = "Hello, " + name; // String concatenation string upper = name.ToUpper(); // Method call ``` ### Rust String Types ```rust // Rust - Two main string types // 1. &str (string slice) - like ReadOnlySpan in C# let name: &str = "John"; // String literal (immutable, borrowed) // 2. String - like StringBuilder or mutable string let mut greeting = String::new(); // Empty string greeting.push_str("Hello, "); // Append greeting.push_str(name); // Append // Or create directly let greeting = String::from("Hello, John"); let greeting = "Hello, John".to_string(); // Convert &str to String ``` ### When to Use Which? | Scenario | Use | C# Equivalent | |----------|-----|---------------| | String literals | `&str` | `string` literal | | Function parameters (read-only) | `&str` | `string` or `ReadOnlySpan` | | Owned, mutable strings | `String` | `StringBuilder` | | Return owned strings | `String` | `string` | ### Practical Examples ```rust // Function that accepts any string type fn greet(name: &str) { // Accepts both String and &str println!("Hello, {}!", name); } fn main() { let literal = "John"; // &str let owned = String::from("Jane"); // String greet(literal); // Works greet(&owned); // Works (borrow String as &str) greet("Bob"); // Works } // Function that returns owned string fn create_greeting(name: &str) -> String { format!("Hello, {}!", name) // format! macro returns String } ``` ### C# Developers: Think of it This Way ```rust // &str is like ReadOnlySpan - a view into string data // String is like a char[] that you own and can modify let borrowed: &str = "I don't own this data"; let owned: String = String::from("I own this data"); // Convert between them let owned_copy: String = borrowed.to_string(); // Copy to owned let borrowed_view: &str = &owned; // Borrow from owned ``` *** ## Comments and Documentation ### Regular Comments ```csharp // C# comments // Single line comment /* Multi-line comment */ /// /// XML documentation comment /// /// The user's name /// A greeting string public string Greet(string name) { return $"Hello, {name}!"; } ``` ```rust // Rust comments // Single line comment /* Multi-line comment */ /// Documentation comment (like C# ///) /// This function greets a user by name. /// /// # Arguments /// /// * `name` - The user's name as a string slice /// /// # Returns /// /// A `String` containing the greeting /// /// # Examples /// /// ``` /// let greeting = greet("Alice"); /// assert_eq!(greeting, "Hello, Alice!"); /// ``` pub fn greet(name: &str) -> String { format!("Hello, {}!", name) } ``` ### Documentation Generation ```bash # Generate documentation (like XML docs in C#) cargo doc --open # Run documentation tests cargo test --doc ``` *** ## Essential Rust Keywords for C# Developers Understanding Rust's keywords and their purposes helps C# developers navigate the language more effectively. ### Visibility and Access Control Keywords #### C# Access Modifiers ```csharp public class Example { public int PublicField; // Accessible everywhere private int privateField; // Only within this class protected int protectedField; // This class and subclasses internal int internalField; // Within this assembly protected internal int protectedInternalField; // Combination } ``` #### Rust Visibility Keywords ```rust // pub - Makes items public (like C# public) pub struct PublicStruct { pub public_field: i32, // Public field private_field: i32, // Private by default (no keyword) } pub mod my_module { pub(crate) fn crate_public() {} // Public within current crate (like internal) pub(super) fn parent_public() {} // Public to parent module pub(self) fn self_public() {} // Public within current module (same as private) pub use super::PublicStruct; // Re-export (like using alias) } // No direct equivalent to C# protected - use composition instead ``` ### Memory and Ownership Keywords #### C# Memory Keywords ```csharp // ref - Pass by reference public void Method(ref int value) { value = 10; } // out - Output parameter public bool TryParse(string input, out int result) { /* */ } // in - Readonly reference (C# 7.2+) public void ReadOnly(in LargeStruct data) { /* Cannot modify data */ } ``` #### Rust Ownership Keywords ```rust // & - Immutable reference (like C# in parameter) fn read_only(data: &Vec) { println!("Length: {}", data.len()); // Can read, cannot modify } // &mut - Mutable reference (like C# ref parameter) fn modify(data: &mut Vec) { data.push(42); // Can modify } // move - Force move capture in closures let data = vec![1, 2, 3]; let closure = move || { println!("{:?}", data); // data is moved into closure }; // data is no longer accessible here // Box - Heap allocation (like C# new for reference types) let boxed_data = Box::new(42); // Allocate on heap ``` ### Control Flow Keywords #### C# Control Flow ```csharp // return - Exit function with value public int GetValue() { return 42; } // yield return - Iterator pattern public IEnumerable GetNumbers() { yield return 1; yield return 2; } // break/continue - Loop control foreach (var item in items) { if (item == null) continue; if (item.Stop) break; } ``` #### Rust Control Flow Keywords ```rust // return - Explicit return (usually not needed) fn get_value() -> i32 { return 42; // Explicit return // OR just: 42 (implicit return) } // break/continue - Loop control with optional values fn find_value() -> Option { loop { let value = get_next(); if value < 0 { continue; } if value > 100 { break None; } // Break with value if value == 42 { break Some(value); } // Break with success } } // loop - Infinite loop (like while(true)) loop { if condition { break; } } // while - Conditional loop while condition { // code } // for - Iterator loop for item in collection { // code } ``` ### Type Definition Keywords #### C# Type Keywords ```csharp // class - Reference type public class MyClass { } // struct - Value type public struct MyStruct { } // interface - Contract definition public interface IMyInterface { } // enum - Enumeration public enum MyEnum { Value1, Value2 } // delegate - Function pointer public delegate void MyDelegate(int value); ``` #### Rust Type Keywords ```rust // struct - Data structure (like C# class/struct combined) struct MyStruct { field: i32, } // enum - Algebraic data type (much more powerful than C# enum) enum MyEnum { Variant1, Variant2(i32), // Can hold data Variant3 { x: i32, y: i32 }, // Struct-like variant } // trait - Interface definition (like C# interface but more powerful) trait MyTrait { fn method(&self); // Default implementation (like C# 8+ default interface methods) fn default_method(&self) { println!("Default implementation"); } } // type - Type alias (like C# using alias) type UserId = u32; type Result = std::result::Result; // impl - Implementation block (no C# equivalent - methods defined separately) impl MyStruct { fn new() -> MyStruct { MyStruct { field: 0 } } } impl MyTrait for MyStruct { fn method(&self) { println!("Implementation"); } } ``` ### Function Definition Keywords #### C# Function Keywords ```csharp // static - Class method public static void StaticMethod() { } // virtual - Can be overridden public virtual void VirtualMethod() { } // override - Override base method public override void VirtualMethod() { } // abstract - Must be implemented public abstract void AbstractMethod(); // async - Asynchronous method public async Task AsyncMethod() { return await SomeTask(); } ``` #### Rust Function Keywords ```rust // fn - Function definition (like C# method but standalone) fn regular_function() { println!("Hello"); } // const fn - Compile-time function (like C# const but for functions) const fn compile_time_function() -> i32 { 42 // Can be evaluated at compile time } // async fn - Asynchronous function (like C# async) async fn async_function() -> i32 { some_async_operation().await } // unsafe fn - Function that may violate memory safety unsafe fn unsafe_function() { // Can perform unsafe operations } // extern fn - Foreign function interface extern "C" fn c_compatible_function() { // Can be called from C } ``` ### Variable Declaration Keywords #### C# Variable Keywords ```csharp // var - Type inference var name = "John"; // Inferred as string // const - Compile-time constant const int MaxSize = 100; // readonly - Runtime constant readonly DateTime createdAt = DateTime.Now; // static - Class-level variable static int instanceCount = 0; ``` #### Rust Variable Keywords ```rust // let - Variable binding (like C# var) let name = "John"; // Immutable by default // let mut - Mutable variable binding let mut count = 0; // Can be changed count += 1; // const - Compile-time constant (like C# const) const MAX_SIZE: usize = 100; // static - Global variable (like C# static) static INSTANCE_COUNT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); ``` ### Pattern Matching Keywords #### C# Pattern Matching (C# 8+) ```csharp // switch expression string result = value switch { 1 => "One", 2 => "Two", _ => "Other" }; // is pattern if (obj is string str) { Console.WriteLine(str.Length); } ``` #### Rust Pattern Matching Keywords ```rust // match - Pattern matching (like C# switch but much more powerful) let result = match value { 1 => "One", 2 => "Two", 3..=10 => "Between 3 and 10", // Range patterns _ => "Other", // Wildcard (like C# _) }; // if let - Conditional pattern matching if let Some(value) = optional { println!("Got value: {}", value); } // while let - Loop with pattern matching while let Some(item) = iterator.next() { println!("Item: {}", item); } // let with patterns - Destructuring let (x, y) = point; // Destructure tuple let Some(value) = optional else { return; // Early return if pattern doesn't match }; ``` ### Memory Safety Keywords #### C# Memory Keywords ```csharp // unsafe - Disable safety checks unsafe { int* ptr = &variable; *ptr = 42; } // fixed - Pin managed memory unsafe { fixed (byte* ptr = array) { // Use ptr } } ``` #### Rust Safety Keywords ```rust // unsafe - Disable borrow checker (use sparingly!) unsafe { let ptr = &variable as *const i32; let value = *ptr; // Dereference raw pointer } // Raw pointer types (no C# equivalent - usually not needed) let ptr: *const i32 = &42; // Immutable raw pointer let ptr: *mut i32 = &mut 42; // Mutable raw pointer ``` ### Common Rust Keywords Not in C# ```rust // where - Generic constraints (more flexible than C# where) fn generic_function() where T: Clone + Send + Sync, { // T must implement Clone, Send, and Sync traits } // dyn - Dynamic trait objects (like C# object but type-safe) let drawable: Box = Box::new(Circle::new()); // Self - Refer to the implementing type (like C# this but for types) impl MyStruct { fn new() -> Self { // Self = MyStruct Self { field: 0 } } } // self - Method receiver impl MyStruct { fn method(&self) { } // Immutable borrow fn method_mut(&mut self) { } // Mutable borrow fn consume(self) { } // Take ownership } // crate - Refer to current crate root use crate::models::User; // Absolute path from crate root // super - Refer to parent module use super::utils; // Import from parent module ``` ### Keywords Summary for C# Developers | Purpose | C# | Rust | Key Difference | |---------|----|----|----------------| | Visibility | `public`, `private`, `internal` | `pub`, default private | More granular with `pub(crate)` | | Variables | `var`, `readonly`, `const` | `let`, `let mut`, `const` | Immutable by default | | Functions | `method()` | `fn` | Standalone functions | | Types | `class`, `struct`, `interface` | `struct`, `enum`, `trait` | Enums are algebraic types | | Generics | ` where T : IFoo` | ` where T: Foo` | More flexible constraints | | References | `ref`, `out`, `in` | `&`, `&mut` | Compile-time borrow checking | | Patterns | `switch`, `is` | `match`, `if let` | Exhaustive matching required | *** ## Understanding Ownership Ownership is Rust's most unique feature and the biggest conceptual shift for C# developers. Let's approach it step by step. ### C# Memory Model (Review) ```csharp // C# - Automatic memory management public void ProcessData() { var data = new List { 1, 2, 3, 4, 5 }; ProcessList(data); // data is still accessible here Console.WriteLine(data.Count); // Works fine // GC will clean up when no references remain } public void ProcessList(List list) { list.Add(6); // Modifies the original list } ``` ### Rust Ownership Rules 1. **Each value has exactly one owner** 2. **When the owner goes out of scope, the value is dropped** 3. **Ownership can be transferred (moved)** ```rust // Rust - Explicit ownership management fn process_data() { let data = vec![1, 2, 3, 4, 5]; // data owns the vector process_list(data); // Ownership moved to function // println!("{:?}", data); // ❌ Error: data no longer owned here } fn process_list(mut list: Vec) { // list now owns the vector list.push(6); // list is dropped here when function ends } ``` ### Understanding "Move" for C# Developers ```csharp // C# - References are copied, objects stay in place var original = new List { 1, 2, 3 }; var reference = original; // Both variables point to same object original.Add(4); Console.WriteLine(reference.Count); // 4 - same object ``` ```rust // Rust - Ownership is transferred let original = vec![1, 2, 3]; let moved = original; // Ownership transferred // println!("{:?}", original); // ❌ Error: original no longer owns the data println!("{:?}", moved); // βœ… Works: moved now owns the data ``` ### Copy Types vs Move Types ```rust // Copy types (like C# value types) - copied, not moved let x = 5; // i32 implements Copy let y = x; // x is copied to y println!("{}", x); // βœ… Works: x is still valid // Move types (like C# reference types) - moved, not copied let s1 = String::from("hello"); // String doesn't implement Copy let s2 = s1; // s1 is moved to s2 // println!("{}", s1); // ❌ Error: s1 is no longer valid ``` ### Practical Example: Swapping Values ```csharp // C# - Simple reference swapping public void SwapLists(ref List a, ref List b) { var temp = a; a = b; b = temp; } ``` ```rust // Rust - Ownership-aware swapping fn swap_vectors(a: &mut Vec, b: &mut Vec) { std::mem::swap(a, b); // Built-in swap function } // Or manual approach fn manual_swap() { let mut a = vec![1, 2, 3]; let mut b = vec![4, 5, 6]; let temp = a; // Move a to temp a = b; // Move b to a b = temp; // Move temp to b println!("a: {:?}, b: {:?}", a, b); } ``` *** ## Borrowing Basics Borrowing is like getting a reference in C#, but with compile-time safety guarantees. ### C# Reference Parameters ```csharp // C# - ref and out parameters public void ModifyValue(ref int value) { value += 10; } public void ReadValue(in int value) // readonly reference { Console.WriteLine(value); } public bool TryParse(string input, out int result) { return int.TryParse(input, out result); } ``` ### Rust Borrowing ```rust // Rust - borrowing with & and &mut fn modify_value(value: &mut i32) { // Mutable borrow *value += 10; } fn read_value(value: &i32) { // Immutable borrow println!("{}", value); } fn main() { let mut x = 5; read_value(&x); // Borrow immutably modify_value(&mut x); // Borrow mutably println!("{}", x); // x is still owned here } ``` ### Borrowing Rules (Enforced at Compile Time!) ```rust fn borrowing_rules() { let mut data = vec![1, 2, 3]; // Rule 1: Multiple immutable borrows are OK let r1 = &data; let r2 = &data; println!("{:?} {:?}", r1, r2); // βœ… Works // Rule 2: Only one mutable borrow at a time let r3 = &mut data; // let r4 = &mut data; // ❌ Error: cannot borrow mutably twice // let r5 = &data; // ❌ Error: cannot borrow immutably while borrowed mutably r3.push(4); // Use the mutable borrow // r3 goes out of scope here // Rule 3: Can borrow again after previous borrows end let r6 = &data; // βœ… Works now println!("{:?}", r6); } ``` ### C# vs Rust: Reference Safety ```csharp // C# - Potential runtime errors public class ReferenceSafety { private List data = new List(); public List GetData() => data; // Returns reference to internal data public void UnsafeExample() { var reference = GetData(); // Another thread could modify data here! Thread.Sleep(1000); // reference might be invalid or changed reference.Add(42); // Potential race condition } } ``` ```rust // Rust - Compile-time safety pub struct SafeContainer { data: Vec, } impl SafeContainer { // Return immutable borrow - caller can't modify pub fn get_data(&self) -> &Vec { &self.data } // Return mutable borrow - exclusive access guaranteed pub fn get_data_mut(&mut self) -> &mut Vec { &mut self.data } } fn safe_example() { let mut container = SafeContainer { data: vec![1, 2, 3] }; let reference = container.get_data(); // container.get_data_mut(); // ❌ Error: can't borrow mutably while immutably borrowed println!("{:?}", reference); // Use immutable reference // reference goes out of scope here let mut_reference = container.get_data_mut(); // βœ… Now OK mut_reference.push(4); } ``` *** ## References vs Pointers ### C# Pointers (Unsafe Context) ```csharp // C# unsafe pointers (rarely used) unsafe void UnsafeExample() { int value = 42; int* ptr = &value; // Pointer to value *ptr = 100; // Dereference and modify Console.WriteLine(value); // 100 } ``` ### Rust References (Safe by Default) ```rust // Rust references (always safe) fn safe_example() { let mut value = 42; let ptr = &mut value; // Mutable reference *ptr = 100; // Dereference and modify println!("{}", value); // 100 } // No "unsafe" keyword needed - borrow checker ensures safety ``` ### Lifetime Basics for C# Developers ```csharp // C# - Can return references that might become invalid public class LifetimeIssues { public string GetFirstWord(string input) { return input.Split(' ')[0]; // Returns new string (safe) } public unsafe char* GetFirstChar(string input) { // This would be dangerous - returning pointer to managed memory fixed (char* ptr = input) return ptr; // ❌ Bad: ptr becomes invalid after method ends } } ``` ```rust // Rust - Lifetime checking prevents dangling references fn get_first_word(input: &str) -> &str { input.split_whitespace().next().unwrap_or("") // βœ… Safe: returned reference has same lifetime as input } fn invalid_reference() -> &str { let temp = String::from("hello"); &temp // ❌ Compile error: temp doesn't live long enough // temp would be dropped at end of function } fn valid_reference() -> String { let temp = String::from("hello"); temp // βœ… Works: ownership is transferred to caller } ``` *** ## Move Semantics ### C# Value Types vs Reference Types ```csharp // C# - Value types are copied struct Point { public int X { get; set; } public int Y { get; set; } } var p1 = new Point { X = 1, Y = 2 }; var p2 = p1; // Copy p2.X = 10; Console.WriteLine(p1.X); // Still 1 // C# - Reference types share the object var list1 = new List { 1, 2, 3 }; var list2 = list1; // Reference copy list2.Add(4); Console.WriteLine(list1.Count); // 4 - same object ``` ### Rust Move Semantics ```rust // Rust - Move by default for non-Copy types #[derive(Debug)] struct Point { x: i32, y: i32, } fn move_example() { let p1 = Point { x: 1, y: 2 }; let p2 = p1; // Move (not copy) // println!("{:?}", p1); // ❌ Error: p1 was moved println!("{:?}", p2); // βœ… Works } // To enable copying, implement Copy trait #[derive(Debug, Copy, Clone)] struct CopyablePoint { x: i32, y: i32, } fn copy_example() { let p1 = CopyablePoint { x: 1, y: 2 }; let p2 = p1; // Copy (because it implements Copy) println!("{:?}", p1); // βœ… Works println!("{:?}", p2); // βœ… Works } ``` ### When Values Are Moved ```rust fn demonstrate_moves() { let s = String::from("hello"); // 1. Assignment moves let s2 = s; // s moved to s2 // 2. Function calls move take_ownership(s2); // s2 moved into function // 3. Returning from functions moves let s3 = give_ownership(); // Return value moved to s3 println!("{}", s3); // s3 is valid } fn take_ownership(s: String) { println!("{}", s); // s is dropped here } fn give_ownership() -> String { String::from("yours") // Ownership moved to caller } ``` ### Avoiding Moves with Borrowing ```rust fn demonstrate_borrowing() { let s = String::from("hello"); // Borrow instead of move let len = calculate_length(&s); // s is borrowed println!("'{}' has length {}", s, len); // s is still valid } fn calculate_length(s: &String) -> usize { s.len() // s is not owned, so it's not dropped } ``` *** ## Functions vs Methods ### C# Function Declaration ```csharp // C# - Methods in classes public class Calculator { // Instance method public int Add(int a, int b) { return a + b; } // Static method public static int Multiply(int a, int b) { return a * b; } // Method with ref parameter public void Increment(ref int value) { value++; } } ``` ### Rust Function Declaration ```rust // Rust - Standalone functions fn add(a: i32, b: i32) -> i32 { a + b // No 'return' needed for final expression } fn multiply(a: i32, b: i32) -> i32 { return a * b; // Explicit return is also fine } // Function with mutable reference fn increment(value: &mut i32) { *value += 1; } fn main() { let result = add(5, 3); println!("5 + 3 = {}", result); let mut x = 10; increment(&mut x); println!("After increment: {}", x); } ``` ### Expression vs Statement (Important!) ```csharp // C# - Statements vs expressions public int GetValue() { if (condition) { return 42; // Statement } return 0; // Statement } ``` ```rust // Rust - Everything can be an expression fn get_value(condition: bool) -> i32 { if condition { 42 // Expression (no semicolon) } else { 0 // Expression (no semicolon) } // The if-else block itself is an expression that returns a value } // Or even simpler fn get_value_ternary(condition: bool) -> i32 { if condition { 42 } else { 0 } } ``` ### Function Parameters and Return Types ```rust // No parameters, no return value (returns unit type ()) fn say_hello() { println!("Hello!"); } // Multiple parameters fn greet(name: &str, age: u32) { println!("{} is {} years old", name, age); } // Multiple return values using tuple fn divide_and_remainder(dividend: i32, divisor: i32) -> (i32, i32) { (dividend / divisor, dividend % divisor) } fn main() { let (quotient, remainder) = divide_and_remainder(10, 3); println!("10 Γ· 3 = {} remainder {}", quotient, remainder); } ``` *** ## Control Flow Basics ### Conditional Statements ```csharp // C# if statements int x = 5; if (x > 10) { Console.WriteLine("Big number"); } else if (x > 5) { Console.WriteLine("Medium number"); } else { Console.WriteLine("Small number"); } // C# ternary operator string message = x > 10 ? "Big" : "Small"; ``` ```rust // Rust if expressions let x = 5; if x > 10 { println!("Big number"); } else if x > 5 { println!("Medium number"); } else { println!("Small number"); } // Rust if as expression (like ternary) let message = if x > 10 { "Big" } else { "Small" }; // Multiple conditions let message = if x > 10 { "Big" } else if x > 5 { "Medium" } else { "Small" }; ``` ### Loops ```csharp // C# loops // For loop for (int i = 0; i < 5; i++) { Console.WriteLine(i); } // Foreach loop var numbers = new[] { 1, 2, 3, 4, 5 }; foreach (var num in numbers) { Console.WriteLine(num); } // While loop int count = 0; while (count < 3) { Console.WriteLine(count); count++; } ``` ```rust // Rust loops // Range-based for loop for i in 0..5 { // 0 to 4 (exclusive end) println!("{}", i); } // Iterate over collection let numbers = vec![1, 2, 3, 4, 5]; for num in numbers { // Takes ownership println!("{}", num); } // Iterate over references (more common) let numbers = vec![1, 2, 3, 4, 5]; for num in &numbers { // Borrows elements println!("{}", num); } // While loop let mut count = 0; while count < 3 { println!("{}", count); count += 1; } // Infinite loop with break let mut counter = 0; loop { if counter >= 3 { break; } println!("{}", counter); counter += 1; } ``` ### Loop Control ```csharp // C# loop control for (int i = 0; i < 10; i++) { if (i == 3) continue; if (i == 7) break; Console.WriteLine(i); } ``` ```rust // Rust loop control for i in 0..10 { if i == 3 { continue; } if i == 7 { break; } println!("{}", i); } // Loop labels (for nested loops) 'outer: for i in 0..3 { 'inner: for j in 0..3 { if i == 1 && j == 1 { break 'outer; // Break out of outer loop } println!("i: {}, j: {}", i, j); } } ``` *** ## Pattern Matching Introduction Pattern matching is much more powerful in Rust than switch statements in C#. ### C# Switch Statements ```csharp // C# traditional switch int value = 2; switch (value) { case 1: Console.WriteLine("One"); break; case 2: Console.WriteLine("Two"); break; default: Console.WriteLine("Other"); break; } // C# 8+ switch expressions string result = value switch { 1 => "One", 2 => "Two", _ => "Other" }; ``` ### Rust Match Expressions ```rust // Rust match (must be exhaustive) let value = 2; match value { 1 => println!("One"), 2 => println!("Two"), _ => println!("Other"), // _ is wildcard (like default) } // Match as expression (like switch expression) let result = match value { 1 => "One", 2 => "Two", _ => "Other", }; // Match multiple values match value { 1 | 2 => println!("One or Two"), // Multiple patterns 3..=5 => println!("Three to Five"), // Range pattern _ => println!("Other"), } ``` ### Destructuring with Match ```csharp // C# tuple deconstruction var point = (3, 4); var (x, y) = point; Console.WriteLine($"x: {x}, y: {y}"); // C# pattern matching with tuples string classify = point switch { (0, 0) => "Origin", (var a, 0) => $"On X-axis at {a}", (0, var b) => $"On Y-axis at {b}", _ => "Somewhere else" }; ``` ```rust // Rust tuple destructuring with match let point = (3, 4); match point { (0, 0) => println!("Origin"), (x, 0) => println!("On X-axis at {}", x), (0, y) => println!("On Y-axis at {}", y), (x, y) => println!("Point at ({}, {})", x, y), } // Match guards (conditions) match point { (x, y) if x == y => println!("On diagonal"), (x, y) if x > y => println!("Above diagonal"), _ => println!("Below diagonal"), } ``` *** ## Error Handling Basics This is a fundamental shift from C#'s exception model to Rust's explicit error handling. ### C# Exception Handling ```csharp // C# - Exception-based error handling public class FileProcessor { public string ReadConfig(string path) { try { return File.ReadAllText(path); } catch (FileNotFoundException) { throw new InvalidOperationException("Config file not found"); } catch (UnauthorizedAccessException) { throw new InvalidOperationException("Cannot access config file"); } } public int ParseNumber(string input) { if (int.TryParse(input, out int result)) { return result; } throw new ArgumentException("Invalid number format"); } } ``` ### Rust Result-Based Error Handling ```rust use std::fs; use std::num::ParseIntError; // Define custom error type #[derive(Debug)] enum ConfigError { FileNotFound, AccessDenied, InvalidFormat, } // Function that returns Result fn read_config(path: &str) -> Result { match fs::read_to_string(path) { Ok(content) => Ok(content), Err(_) => Err(ConfigError::FileNotFound), // Simplified for example } } // Function that can fail fn parse_number(input: &str) -> Result { input.parse::() // Returns Result } fn main() { // Handle errors explicitly match read_config("config.txt") { Ok(content) => println!("Config: {}", content), Err(ConfigError::FileNotFound) => println!("Config file not found"), Err(error) => println!("Config error: {:?}", error), } // Handle parsing errors match parse_number("42") { Ok(num) => println!("Number: {}", num), Err(error) => println!("Parse error: {}", error), } } ``` ### The ? Operator (Like C#'s await) ```csharp // C# - Exception propagation (implicit) public async Task ProcessFileAsync(string path) { var content = await File.ReadAllTextAsync(path); // Throws on error var processed = ProcessContent(content); // Throws on error return processed; } ``` ```rust // Rust - Error propagation with ? fn process_file(path: &str) -> Result { let content = read_config(path)?; // ? propagates error if Err let processed = process_content(&content)?; // ? propagates error if Err Ok(processed) // Wrap success value in Ok } fn process_content(content: &str) -> Result { if content.is_empty() { Err(ConfigError::InvalidFormat) } else { Ok(content.to_uppercase()) } } ``` ### Option for Nullable Values ```csharp // C# - Nullable reference types public string? FindUserName(int userId) { var user = database.FindUser(userId); return user?.Name; // Returns null if user not found } public void ProcessUser(int userId) { string? name = FindUserName(userId); if (name != null) { Console.WriteLine($"User: {name}"); } else { Console.WriteLine("User not found"); } } ``` ```rust // Rust - Option for optional values fn find_user_name(user_id: u32) -> Option { // Simulate database lookup if user_id == 1 { Some("Alice".to_string()) } else { None } } fn process_user(user_id: u32) { match find_user_name(user_id) { Some(name) => println!("User: {}", name), None => println!("User not found"), } // Or use if let (pattern matching shorthand) if let Some(name) = find_user_name(user_id) { println!("User: {}", name); } else { println!("User not found"); } } ``` ### Combining Option and Result ```rust fn safe_divide(a: f64, b: f64) -> Option { if b != 0.0 { Some(a / b) } else { None } } fn parse_and_divide(a_str: &str, b_str: &str) -> Result, ParseFloatError> { let a: f64 = a_str.parse()?; // Return parse error if invalid let b: f64 = b_str.parse()?; // Return parse error if invalid Ok(safe_divide(a, b)) // Return Ok(Some(result)) or Ok(None) } use std::num::ParseFloatError; fn main() { match parse_and_divide("10.0", "2.0") { Ok(Some(result)) => println!("Result: {}", result), Ok(None) => println!("Division by zero"), Err(error) => println!("Parse error: {}", error), } } ``` *** ## Vec vs List Vec is Rust's equivalent to C#'s List, but with ownership semantics. ### C# List ```csharp // C# List - Reference type, heap allocated var numbers = new List(); numbers.Add(1); numbers.Add(2); numbers.Add(3); // Pass to method - reference is copied ProcessList(numbers); Console.WriteLine(numbers.Count); // Still accessible void ProcessList(List list) { list.Add(4); // Modifies original list Console.WriteLine($"Count in method: {list.Count}"); } ``` ### Rust Vec ```rust // Rust Vec - Owned type, heap allocated let mut numbers = Vec::new(); numbers.push(1); numbers.push(2); numbers.push(3); // Method that takes ownership process_vec(numbers); // println!("{:?}", numbers); // ❌ Error: numbers was moved // Method that borrows let mut numbers = vec![1, 2, 3]; // vec! macro for convenience process_vec_borrowed(&mut numbers); println!("{:?}", numbers); // βœ… Still accessible fn process_vec(mut vec: Vec) { // Takes ownership vec.push(4); println!("Count in method: {}", vec.len()); // vec is dropped here } fn process_vec_borrowed(vec: &mut Vec) { // Borrows mutably vec.push(4); println!("Count in method: {}", vec.len()); } ``` ### Creating and Initializing Vectors ```csharp // C# List initialization var numbers = new List { 1, 2, 3, 4, 5 }; var empty = new List(); var sized = new List(10); // Initial capacity // From other collections var fromArray = new List(new[] { 1, 2, 3 }); ``` ```rust // Rust Vec initialization let numbers = vec![1, 2, 3, 4, 5]; // vec! macro let empty: Vec = Vec::new(); // Type annotation needed for empty let sized = Vec::with_capacity(10); // Pre-allocate capacity // From iterator let from_range: Vec = (1..=5).collect(); let from_array = vec![1, 2, 3]; ``` ### Common Operations Comparison ```csharp // C# List operations var list = new List { 1, 2, 3 }; list.Add(4); // Add element list.Insert(0, 0); // Insert at index list.Remove(2); // Remove first occurrence list.RemoveAt(1); // Remove at index list.Clear(); // Remove all int first = list[0]; // Index access int count = list.Count; // Get count bool contains = list.Contains(3); // Check if contains ``` ```rust // Rust Vec operations let mut vec = vec![1, 2, 3]; vec.push(4); // Add element vec.insert(0, 0); // Insert at index vec.retain(|&x| x != 2); // Remove elements (functional style) vec.remove(1); // Remove at index vec.clear(); // Remove all let first = vec[0]; // Index access (panics if out of bounds) let safe_first = vec.get(0); // Safe access, returns Option<&T> let count = vec.len(); // Get count let contains = vec.contains(&3); // Check if contains ``` ### Safe Access Patterns ```csharp // C# - Exception-based bounds checking public int SafeAccess(List list, int index) { try { return list[index]; } catch (ArgumentOutOfRangeException) { return -1; // Default value } } ``` ```rust // Rust - Option-based safe access fn safe_access(vec: &Vec, index: usize) -> Option { vec.get(index).copied() // Returns Option } fn main() { let vec = vec![1, 2, 3]; // Safe access patterns match vec.get(10) { Some(value) => println!("Value: {}", value), None => println!("Index out of bounds"), } // Or with unwrap_or let value = vec.get(10).copied().unwrap_or(-1); println!("Value: {}", value); } ``` *** ## HashMap vs Dictionary HashMap is Rust's equivalent to C#'s Dictionary. ### C# Dictionary ```csharp // C# Dictionary var scores = new Dictionary { ["Alice"] = 100, ["Bob"] = 85, ["Charlie"] = 92 }; // Add/Update scores["Dave"] = 78; scores["Alice"] = 105; // Update existing // Safe access if (scores.TryGetValue("Eve", out int score)) { Console.WriteLine($"Eve's score: {score}"); } else { Console.WriteLine("Eve not found"); } // Iteration foreach (var kvp in scores) { Console.WriteLine($"{kvp.Key}: {kvp.Value}"); } ``` ### Rust HashMap ```rust use std::collections::HashMap; // Create and initialize HashMap let mut scores = HashMap::new(); scores.insert("Alice".to_string(), 100); scores.insert("Bob".to_string(), 85); scores.insert("Charlie".to_string(), 92); // Or use from iterator let scores: HashMap = [ ("Alice".to_string(), 100), ("Bob".to_string(), 85), ("Charlie".to_string(), 92), ].into_iter().collect(); // Add/Update let mut scores = scores; // Make mutable scores.insert("Dave".to_string(), 78); scores.insert("Alice".to_string(), 105); // Update existing // Safe access match scores.get("Eve") { Some(score) => println!("Eve's score: {}", score), None => println!("Eve not found"), } // Iteration for (name, score) in &scores { println!("{}: {}", name, score); } ``` ### HashMap Operations ```csharp // C# Dictionary operations var dict = new Dictionary(); dict["key"] = 42; // Insert/update bool exists = dict.ContainsKey("key"); // Check existence bool removed = dict.Remove("key"); // Remove dict.Clear(); // Clear all // Get with default int value = dict.GetValueOrDefault("missing", 0); ``` ```rust use std::collections::HashMap; // Rust HashMap operations let mut map = HashMap::new(); map.insert("key".to_string(), 42); // Insert/update let exists = map.contains_key("key"); // Check existence let removed = map.remove("key"); // Remove, returns Option map.clear(); // Clear all // Entry API for advanced operations let mut map = HashMap::new(); map.entry("key".to_string()).or_insert(42); // Insert if not exists map.entry("key".to_string()).and_modify(|v| *v += 1); // Modify if exists // Get with default let value = map.get("missing").copied().unwrap_or(0); ``` ### Ownership with HashMap Keys and Values ```rust // Understanding ownership with HashMap fn ownership_example() { let mut map = HashMap::new(); // String keys and values are moved into the map let key = String::from("name"); let value = String::from("Alice"); map.insert(key, value); // println!("{}", key); // ❌ Error: key was moved // println!("{}", value); // ❌ Error: value was moved // Access via references if let Some(name) = map.get("name") { println!("Name: {}", name); // Borrowing the value } } // Using &str keys (no ownership transfer) fn string_slice_keys() { let mut map = HashMap::new(); map.insert("name", "Alice"); // &str keys and values map.insert("age", "30"); // No ownership issues with string literals println!("Name exists: {}", map.contains_key("name")); } ``` *** ## Arrays and Slices Understanding the difference between arrays, slices, and vectors is crucial. ### C# Arrays ```csharp // C# arrays int[] numbers = new int[5]; // Fixed size, heap allocated int[] initialized = { 1, 2, 3, 4, 5 }; // Array literal // Access numbers[0] = 10; int first = numbers[0]; // Length int length = numbers.Length; // Array as parameter (reference type) void ProcessArray(int[] array) { array[0] = 99; // Modifies original } ``` ### Rust Arrays, Slices, and Vectors ```rust // 1. Arrays - Fixed size, stack allocated let numbers: [i32; 5] = [1, 2, 3, 4, 5]; // Type: [i32; 5] let zeros = [0; 10]; // 10 zeros // Access let first = numbers[0]; // numbers[0] = 10; // ❌ Error: arrays are immutable by default let mut mut_array = [1, 2, 3, 4, 5]; mut_array[0] = 10; // βœ… Works with mut // 2. Slices - Views into arrays or vectors let slice: &[i32] = &numbers[1..4]; // Elements 1, 2, 3 let all_slice: &[i32] = &numbers; // Entire array as slice // 3. Vectors - Dynamic size, heap allocated (covered earlier) let mut vec = vec![1, 2, 3, 4, 5]; vec.push(6); // Can grow ``` ### Slices as Function Parameters ```csharp // C# - Method that works with arrays public void ProcessNumbers(int[] numbers) { for (int i = 0; i < numbers.Length; i++) { Console.WriteLine(numbers[i]); } } // Works with arrays only ProcessNumbers(new int[] { 1, 2, 3 }); ``` ```rust // Rust - Function that works with any sequence fn process_numbers(numbers: &[i32]) { // Slice parameter for (i, num) in numbers.iter().enumerate() { println!("Index {}: {}", i, num); } } fn main() { let array = [1, 2, 3, 4, 5]; let vec = vec![1, 2, 3, 4, 5]; // Same function works with both! process_numbers(&array); // Array as slice process_numbers(&vec); // Vector as slice process_numbers(&vec[1..4]); // Partial slice } ``` ### String Slices (&str) Revisited ```rust // String and &str relationship fn string_slice_example() { let owned = String::from("Hello, World!"); let slice: &str = &owned[0..5]; // "Hello" let slice2: &str = &owned[7..]; // "World!" println!("{}", slice); // "Hello" println!("{}", slice2); // "World!" // Function that accepts any string type print_string("String literal"); // &str print_string(&owned); // String as &str print_string(slice); // &str slice } fn print_string(s: &str) { println!("{}", s); } ``` *** ## Working with Collections ### Iteration Patterns ```csharp // C# iteration patterns var numbers = new List { 1, 2, 3, 4, 5 }; // For loop with index for (int i = 0; i < numbers.Count; i++) { Console.WriteLine($"Index {i}: {numbers[i]}"); } // Foreach loop foreach (int num in numbers) { Console.WriteLine(num); } // LINQ methods var doubled = numbers.Select(x => x * 2).ToList(); var evens = numbers.Where(x => x % 2 == 0).ToList(); ``` ```rust // Rust iteration patterns let numbers = vec![1, 2, 3, 4, 5]; // For loop with index for (i, num) in numbers.iter().enumerate() { println!("Index {}: {}", i, num); } // For loop over values for num in &numbers { // Borrow each element println!("{}", num); } // Iterator methods (like LINQ) let doubled: Vec = numbers.iter().map(|x| x * 2).collect(); let evens: Vec = numbers.iter().filter(|&x| x % 2 == 0).cloned().collect(); // Or more efficiently, consuming iterator let doubled: Vec = numbers.into_iter().map(|x| x * 2).collect(); ``` ### Iterator vs IntoIterator vs Iter ```rust // Understanding different iteration methods fn iteration_methods() { let vec = vec![1, 2, 3, 4, 5]; // 1. iter() - borrows elements (&T) for item in vec.iter() { println!("{}", item); // item is &i32 } // vec is still usable here // 2. into_iter() - takes ownership (T) for item in vec.into_iter() { println!("{}", item); // item is i32 } // vec is no longer usable here let mut vec = vec![1, 2, 3, 4, 5]; // 3. iter_mut() - mutable borrows (&mut T) for item in vec.iter_mut() { *item *= 2; // item is &mut i32 } println!("{:?}", vec); // [2, 4, 6, 8, 10] } ``` ### Collecting Results ```csharp // C# - Processing collections with potential errors public List ParseNumbers(List inputs) { var results = new List(); foreach (string input in inputs) { if (int.TryParse(input, out int result)) { results.Add(result); } // Silently skip invalid inputs } return results; } ``` ```rust // Rust - Explicit error handling with collect fn parse_numbers(inputs: Vec) -> Result, std::num::ParseIntError> { inputs.into_iter() .map(|s| s.parse::()) // Returns Result .collect() // Collects into Result, ParseIntError> } // Alternative: Filter out errors fn parse_numbers_filter(inputs: Vec) -> Vec { inputs.into_iter() .filter_map(|s| s.parse::().ok()) // Keep only Ok values .collect() } fn main() { let inputs = vec!["1".to_string(), "2".to_string(), "invalid".to_string(), "4".to_string()]; // Version that fails on first error match parse_numbers(inputs.clone()) { Ok(numbers) => println!("All parsed: {:?}", numbers), Err(error) => println!("Parse error: {}", error), } // Version that skips errors let numbers = parse_numbers_filter(inputs); println!("Successfully parsed: {:?}", numbers); // [1, 2, 4] } ``` *** ## Structs vs Classes Structs in Rust are similar to classes in C#, but with some key differences around ownership and methods. ### C# Class Definition ```csharp // C# class with properties and methods public class Person { public string Name { get; set; } public int Age { get; set; } public List Hobbies { get; set; } public Person(string name, int age) { Name = name; Age = age; Hobbies = new List(); } public void AddHobby(string hobby) { Hobbies.Add(hobby); } public string GetInfo() { return $"{Name} is {Age} years old"; } } ``` ### Rust Struct Definition ```rust // Rust struct with associated functions and methods #[derive(Debug)] // Automatically implement Debug trait pub struct Person { pub name: String, // Public field pub age: u32, // Public field hobbies: Vec, // Private field (no pub) } impl Person { // Associated function (like static method) pub fn new(name: String, age: u32) -> Person { Person { name, age, hobbies: Vec::new(), } } // Method (takes &self, &mut self, or self) pub fn add_hobby(&mut self, hobby: String) { self.hobbies.push(hobby); } // Method that borrows immutably pub fn get_info(&self) -> String { format!("{} is {} years old", self.name, self.age) } // Getter for private field pub fn hobbies(&self) -> &Vec { &self.hobbies } } ``` ### Creating and Using Instances ```csharp // C# object creation and usage var person = new Person("Alice", 30); person.AddHobby("Reading"); person.AddHobby("Swimming"); Console.WriteLine(person.GetInfo()); Console.WriteLine($"Hobbies: {string.Join(", ", person.Hobbies)}"); // Modify properties directly person.Age = 31; ``` ```rust // Rust struct creation and usage let mut person = Person::new("Alice".to_string(), 30); person.add_hobby("Reading".to_string()); person.add_hobby("Swimming".to_string()); println!("{}", person.get_info()); println!("Hobbies: {:?}", person.hobbies()); // Modify public fields directly person.age = 31; // Debug print the entire struct println!("{:?}", person); ``` ### Struct Initialization Patterns ```csharp // C# object initialization var person = new Person("Bob", 25) { Hobbies = new List { "Gaming", "Coding" } }; // Anonymous types var anonymous = new { Name = "Charlie", Age = 35 }; ``` ```rust // Rust struct initialization let person = Person { name: "Bob".to_string(), age: 25, hobbies: vec!["Gaming".to_string(), "Coding".to_string()], }; // Struct update syntax (like object spread) let older_person = Person { age: 26, ..person // Use remaining fields from person (moves person!) }; // Tuple structs (like anonymous types) #[derive(Debug)] struct Point(i32, i32); let point = Point(10, 20); println!("Point: ({}, {})", point.0, point.1); ``` *** ## Methods and Associated Functions Understanding the difference between methods and associated functions is key. ### C# Method Types ```csharp public class Calculator { private int memory = 0; // Instance method public int Add(int a, int b) { return a + b; } // Instance method that uses state public void StoreInMemory(int value) { memory = value; } // Static method public static int Multiply(int a, int b) { return a * b; } // Static factory method public static Calculator CreateWithMemory(int initialMemory) { var calc = new Calculator(); calc.memory = initialMemory; return calc; } } ``` ### Rust Method Types ```rust #[derive(Debug)] pub struct Calculator { memory: i32, } impl Calculator { // Associated function (like static method) - no self parameter pub fn new() -> Calculator { Calculator { memory: 0 } } // Associated function with parameters pub fn with_memory(initial_memory: i32) -> Calculator { Calculator { memory: initial_memory } } // Method that borrows immutably (&self) pub fn add(&self, a: i32, b: i32) -> i32 { a + b } // Method that borrows mutably (&mut self) pub fn store_in_memory(&mut self, value: i32) { self.memory = value; } // Method that takes ownership (self) pub fn into_memory(self) -> i32 { self.memory // Calculator is consumed } // Getter method pub fn memory(&self) -> i32 { self.memory } } fn main() { // Associated functions called with :: let mut calc = Calculator::new(); let calc2 = Calculator::with_memory(42); // Methods called with . let result = calc.add(5, 3); calc.store_in_memory(result); println!("Memory: {}", calc.memory()); // Consuming method let memory_value = calc.into_memory(); // calc is no longer usable println!("Final memory: {}", memory_value); } ``` ### Method Receiver Types Explained ```rust impl Person { // &self - Immutable borrow (most common) // Use when you only need to read the data pub fn get_name(&self) -> &str { &self.name } // &mut self - Mutable borrow // Use when you need to modify the data pub fn set_name(&mut self, name: String) { self.name = name; } // self - Take ownership (less common) // Use when you want to consume the struct pub fn consume(self) -> String { self.name // Person is moved, no longer accessible } } fn method_examples() { let mut person = Person::new("Alice".to_string(), 30); // Immutable borrow let name = person.get_name(); // person can still be used println!("Name: {}", name); // Mutable borrow person.set_name("Alice Smith".to_string()); // person can still be used // Taking ownership let final_name = person.consume(); // person is no longer usable println!("Final name: {}", final_name); } ``` *** ## Implementing Behavior ### C# Interface Implementation ```csharp // C# interface public interface IDrawable { void Draw(); double GetArea(); } public class Circle : IDrawable { public double Radius { get; set; } public Circle(double radius) { Radius = radius; } public void Draw() { Console.WriteLine($"Drawing a circle with radius {Radius}"); } public double GetArea() { return Math.PI * Radius * Radius; } } ``` ### Rust Trait Implementation (Preview) ```rust // Rust trait (like interface) trait Drawable { fn draw(&self); fn get_area(&self) -> f64; } #[derive(Debug)] struct Circle { radius: f64, } impl Circle { pub fn new(radius: f64) -> Circle { Circle { radius } } } // Implement trait for Circle impl Drawable for Circle { fn draw(&self) { println!("Drawing a circle with radius {}", self.radius); } fn get_area(&self) -> f64 { std::f64::consts::PI * self.radius * self.radius } } fn main() { let circle = Circle::new(5.0); circle.draw(); println!("Area: {}", circle.get_area()); } ``` ### Multiple Implementations ```csharp // C# - Class implementing multiple interfaces public interface IComparable { int CompareTo(T other); } public class Person : IDrawable, IComparable { public string Name { get; set; } public int Age { get; set; } public void Draw() { Console.WriteLine($"Drawing person: {Name}"); } public double GetArea() { return 0.0; // People don't have area! } public int CompareTo(Person other) { return Age.CompareTo(other.Age); } } ``` ```rust // Rust - Multiple trait implementations use std::cmp::Ordering; impl Drawable for Person { fn draw(&self) { println!("Drawing person: {}", self.name); } fn get_area(&self) -> f64 { 0.0 // People don't have area! } } impl PartialOrd for Person { fn partial_cmp(&self, other: &Self) -> Option { self.age.partial_cmp(&other.age) } } impl PartialEq for Person { fn eq(&self, other: &Self) -> bool { self.age == other.age } } fn main() { let mut people = vec![ Person::new("Alice".to_string(), 30), Person::new("Bob".to_string(), 25), Person::new("Charlie".to_string(), 35), ]; people.sort_by(|a, b| a.partial_cmp(b).unwrap()); for person in &people { person.draw(); } } ``` *** ## Constructor Patterns ### C# Constructor Patterns ```csharp public class Configuration { public string DatabaseUrl { get; set; } public int MaxConnections { get; set; } public bool EnableLogging { get; set; } // Default constructor public Configuration() { DatabaseUrl = "localhost"; MaxConnections = 10; EnableLogging = false; } // Parameterized constructor public Configuration(string databaseUrl, int maxConnections) { DatabaseUrl = databaseUrl; MaxConnections = maxConnections; EnableLogging = false; } // Factory method public static Configuration ForProduction() { return new Configuration("prod.db.server", 100) { EnableLogging = true }; } } ``` ### Rust Constructor Patterns ```rust #[derive(Debug)] pub struct Configuration { pub database_url: String, pub max_connections: u32, pub enable_logging: bool, } impl Configuration { // Default constructor pub fn new() -> Configuration { Configuration { database_url: "localhost".to_string(), max_connections: 10, enable_logging: false, } } // Parameterized constructor pub fn with_database(database_url: String, max_connections: u32) -> Configuration { Configuration { database_url, max_connections, enable_logging: false, } } // Factory method pub fn for_production() -> Configuration { Configuration { database_url: "prod.db.server".to_string(), max_connections: 100, enable_logging: true, } } // Builder pattern method pub fn enable_logging(mut self) -> Configuration { self.enable_logging = true; self // Return self for chaining } pub fn max_connections(mut self, count: u32) -> Configuration { self.max_connections = count; self } } // Default trait implementation impl Default for Configuration { fn default() -> Self { Self::new() } } fn main() { // Different construction patterns let config1 = Configuration::new(); let config2 = Configuration::with_database("localhost:5432".to_string(), 20); let config3 = Configuration::for_production(); // Builder pattern let config4 = Configuration::new() .enable_logging() .max_connections(50); // Using Default trait let config5 = Configuration::default(); println!("{:?}", config4); } ``` ### Builder Pattern Implementation ```rust // More complex builder pattern #[derive(Debug)] pub struct DatabaseConfig { host: String, port: u16, username: String, password: Option, ssl_enabled: bool, timeout_seconds: u64, } pub struct DatabaseConfigBuilder { host: Option, port: Option, username: Option, password: Option, ssl_enabled: bool, timeout_seconds: u64, } impl DatabaseConfigBuilder { pub fn new() -> Self { DatabaseConfigBuilder { host: None, port: None, username: None, password: None, ssl_enabled: false, timeout_seconds: 30, } } pub fn host(mut self, host: impl Into) -> Self { self.host = Some(host.into()); self } pub fn port(mut self, port: u16) -> Self { self.port = Some(port); self } pub fn username(mut self, username: impl Into) -> Self { self.username = Some(username.into()); self } pub fn password(mut self, password: impl Into) -> Self { self.password = Some(password.into()); self } pub fn enable_ssl(mut self) -> Self { self.ssl_enabled = true; self } pub fn timeout(mut self, seconds: u64) -> Self { self.timeout_seconds = seconds; self } pub fn build(self) -> Result { let host = self.host.ok_or("Host is required")?; let port = self.port.ok_or("Port is required")?; let username = self.username.ok_or("Username is required")?; Ok(DatabaseConfig { host, port, username, password: self.password, ssl_enabled: self.ssl_enabled, timeout_seconds: self.timeout_seconds, }) } } fn main() { let config = DatabaseConfigBuilder::new() .host("localhost") .port(5432) .username("admin") .password("secret123") .enable_ssl() .timeout(60) .build() .expect("Failed to build config"); println!("{:?}", config); } ``` *** ## Enums and Pattern Matching Rust enums are much more powerful than C# enums - they can hold data and are the foundation of type-safe programming. ### C# Enum Limitations ```csharp // C# enum - just named constants public enum Status { Pending, Approved, Rejected } // C# enum with backing values public enum HttpStatusCode { OK = 200, NotFound = 404, InternalServerError = 500 } // Need separate classes for complex data public abstract class Result { public abstract bool IsSuccess { get; } } public class Success : Result { public string Value { get; } public override bool IsSuccess => true; public Success(string value) { Value = value; } } public class Error : Result { public string Message { get; } public override bool IsSuccess => false; public Error(string message) { Message = message; } } ``` ### Rust Enum Power ```rust // Simple enum (like C# enum) #[derive(Debug, PartialEq)] enum Status { Pending, Approved, Rejected, } // Enum with data (this is where Rust shines!) #[derive(Debug)] enum Result { Ok(T), // Success variant holding value of type T Err(E), // Error variant holding error of type E } // Complex enum with different data types #[derive(Debug)] enum Message { Quit, // No data Move { x: i32, y: i32 }, // Struct-like variant Write(String), // Tuple-like variant ChangeColor(i32, i32, i32), // Multiple values } // Real-world example: HTTP Response #[derive(Debug)] enum HttpResponse { Ok { body: String, headers: Vec }, NotFound { path: String }, InternalError { message: String, code: u16 }, Redirect { location: String }, } ``` ### Pattern Matching with Match ```csharp // C# switch statement (limited) public string HandleStatus(Status status) { switch (status) { case Status.Pending: return "Waiting for approval"; case Status.Approved: return "Request approved"; case Status.Rejected: return "Request rejected"; default: return "Unknown status"; // Always need default } } // C# pattern matching (C# 8+) public string HandleResult(Result result) { return result switch { Success success => $"Success: {success.Value}", Error error => $"Error: {error.Message}", _ => "Unknown result" // Still need catch-all }; } ``` ```rust // Rust match - exhaustive and powerful fn handle_status(status: Status) -> String { match status { Status::Pending => "Waiting for approval".to_string(), Status::Approved => "Request approved".to_string(), Status::Rejected => "Request rejected".to_string(), // No default needed - compiler ensures exhaustiveness } } // Pattern matching with data extraction fn handle_result(result: Result) -> String where T: std::fmt::Debug, E: std::fmt::Debug, { match result { Result::Ok(value) => format!("Success: {:?}", value), Result::Err(error) => format!("Error: {:?}", error), // Exhaustive - no default needed } } // Complex pattern matching fn handle_message(msg: Message) -> String { match msg { Message::Quit => "Goodbye!".to_string(), Message::Move { x, y } => format!("Move to ({}, {})", x, y), Message::Write(text) => format!("Write: {}", text), Message::ChangeColor(r, g, b) => format!("Change color to RGB({}, {}, {})", r, g, b), } } // HTTP response handling fn handle_http_response(response: HttpResponse) -> String { match response { HttpResponse::Ok { body, headers } => { format!("Success! Body: {}, Headers: {:?}", body, headers) }, HttpResponse::NotFound { path } => { format!("404: Path '{}' not found", path) }, HttpResponse::InternalError { message, code } => { format!("Error {}: {}", code, message) }, HttpResponse::Redirect { location } => { format!("Redirect to: {}", location) }, } } ``` ### Guards and Advanced Patterns ```rust // Pattern matching with guards fn describe_number(x: i32) -> String { match x { n if n < 0 => "negative".to_string(), 0 => "zero".to_string(), n if n < 10 => "single digit".to_string(), n if n < 100 => "double digit".to_string(), _ => "large number".to_string(), } } // Matching ranges fn describe_age(age: u32) -> String { match age { 0..=12 => "child".to_string(), 13..=19 => "teenager".to_string(), 20..=64 => "adult".to_string(), 65.. => "senior".to_string(), } } // Destructuring structs and tuples #[derive(Debug)] struct Point { x: i32, y: i32, } fn describe_point(point: Point) -> String { match point { Point { x: 0, y: 0 } => "origin".to_string(), Point { x: 0, y } => format!("on y-axis at y={}", y), Point { x, y: 0 } => format!("on x-axis at x={}", x), Point { x, y } if x == y => format!("on diagonal at ({}, {})", x, y), Point { x, y } => format!("point at ({}, {})", x, y), } } ``` ### Option and Result Types ```csharp // C# nullable reference types (C# 8+) public class PersonService { private Dictionary people = new(); public string? FindPerson(int id) { return people.TryGetValue(id, out string? name) ? name : null; } public string GetPersonOrDefault(int id) { return FindPerson(id) ?? "Unknown"; } // Exception-based error handling public void SavePerson(int id, string name) { if (string.IsNullOrEmpty(name)) throw new ArgumentException("Name cannot be empty"); people[id] = name; } } ``` ```rust use std::collections::HashMap; // Rust uses Option instead of null struct PersonService { people: HashMap, } impl PersonService { fn new() -> Self { PersonService { people: HashMap::new(), } } // Returns Option - no null! fn find_person(&self, id: i32) -> Option<&String> { self.people.get(&id) } // Pattern matching on Option fn get_person_or_default(&self, id: i32) -> String { match self.find_person(id) { Some(name) => name.clone(), None => "Unknown".to_string(), } } // Using Option methods (more functional style) fn get_person_or_default_functional(&self, id: i32) -> String { self.find_person(id) .map(|name| name.clone()) .unwrap_or_else(|| "Unknown".to_string()) } // Result for error handling fn save_person(&mut self, id: i32, name: String) -> Result<(), String> { if name.is_empty() { return Err("Name cannot be empty".to_string()); } self.people.insert(id, name); Ok(()) } // Chaining operations fn get_person_length(&self, id: i32) -> Option { self.find_person(id).map(|name| name.len()) } } fn main() { let mut service = PersonService::new(); // Handle Result match service.save_person(1, "Alice".to_string()) { Ok(()) => println!("Person saved successfully"), Err(error) => println!("Error: {}", error), } // Handle Option match service.find_person(1) { Some(name) => println!("Found: {}", name), None => println!("Person not found"), } // Functional style with Option let name_length = service.get_person_length(1) .unwrap_or(0); println!("Name length: {}", name_length); // Question mark operator for early returns fn try_operation(service: &mut PersonService) -> Result { service.save_person(2, "Bob".to_string())?; // Early return if error let name = service.find_person(2).ok_or("Person not found")?; // Convert Option to Result Ok(format!("Hello, {}", name)) } match try_operation(&mut service) { Ok(message) => println!("{}", message), Err(error) => println!("Operation failed: {}", error), } } ``` ### Custom Error Types ```rust // Define custom error enum #[derive(Debug)] enum PersonError { NotFound(i32), InvalidName(String), DatabaseError(String), } impl std::fmt::Display for PersonError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { PersonError::NotFound(id) => write!(f, "Person with ID {} not found", id), PersonError::InvalidName(name) => write!(f, "Invalid name: '{}'", name), PersonError::DatabaseError(msg) => write!(f, "Database error: {}", msg), } } } impl std::error::Error for PersonError {} // Enhanced PersonService with custom errors impl PersonService { fn save_person_enhanced(&mut self, id: i32, name: String) -> Result<(), PersonError> { if name.is_empty() || name.len() > 50 { return Err(PersonError::InvalidName(name)); } // Simulate database operation that might fail if id < 0 { return Err(PersonError::DatabaseError("Negative IDs not allowed".to_string())); } self.people.insert(id, name); Ok(()) } fn find_person_enhanced(&self, id: i32) -> Result<&String, PersonError> { self.people.get(&id).ok_or(PersonError::NotFound(id)) } } fn demo_error_handling() { let mut service = PersonService::new(); // Handle different error types match service.save_person_enhanced(-1, "Invalid".to_string()) { Ok(()) => println!("Success"), Err(PersonError::NotFound(id)) => println!("Not found: {}", id), Err(PersonError::InvalidName(name)) => println!("Invalid name: {}", name), Err(PersonError::DatabaseError(msg)) => println!("DB Error: {}", msg), } } ``` *** ## Modules and Crates: Code Organization Understanding Rust's module system is essential for organizing code and managing dependencies. For C# developers, this is analogous to understanding namespaces, assemblies, and NuGet packages. ### Rust Modules vs C# Namespaces #### C# Namespace Organization ```csharp // File: Models/User.cs namespace MyApp.Models { public class User { public string Name { get; set; } public int Age { get; set; } } } // File: Services/UserService.cs using MyApp.Models; namespace MyApp.Services { public class UserService { public User CreateUser(string name, int age) { return new User { Name = name, Age = age }; } } } // File: Program.cs using MyApp.Models; using MyApp.Services; namespace MyApp { class Program { static void Main(string[] args) { var service = new UserService(); var user = service.CreateUser("Alice", 30); } } } ``` #### Rust Module Organization ```rust // File: src/models.rs pub struct User { pub name: String, pub age: u32, } impl User { pub fn new(name: String, age: u32) -> User { User { name, age } } } // File: src/services.rs use crate::models::User; pub struct UserService; impl UserService { pub fn create_user(name: String, age: u32) -> User { User::new(name, age) } } // File: src/lib.rs (or main.rs) pub mod models; pub mod services; use models::User; use services::UserService; fn main() { let service = UserService; let user = UserService::create_user("Alice".to_string(), 30); } ``` ### Module Hierarchy and Visibility #### C# Visibility Modifiers ```csharp namespace MyApp.Data { // public - accessible from anywhere public class Repository { // private - only within this class private string connectionString; // internal - within this assembly internal void Connect() { } // protected - this class and subclasses protected virtual void Initialize() { } // public - accessible from anywhere public void Save(object data) { } } } ``` #### Rust Visibility Rules ```rust // Everything is private by default in Rust mod data { struct Repository { // Private struct connection_string: String, // Private field } impl Repository { fn new() -> Repository { // Private function Repository { connection_string: "localhost".to_string(), } } pub fn connect(&self) { // Public method // Only accessible within this module and its children } pub(crate) fn initialize(&self) { // Crate-level public // Accessible anywhere in this crate } pub(super) fn internal_method(&self) { // Parent module public // Accessible in parent module } } // Public struct - accessible from outside the module pub struct PublicRepository { pub data: String, // Public field private_data: String, // Private field (no pub) } } pub use data::PublicRepository; // Re-export for external use ``` ### Module File Organization #### C# Project Structure ``` MyApp/ β”œβ”€β”€ MyApp.csproj β”œβ”€β”€ Models/ β”‚ β”œβ”€β”€ User.cs β”‚ └── Product.cs β”œβ”€β”€ Services/ β”‚ β”œβ”€β”€ UserService.cs β”‚ └── ProductService.cs β”œβ”€β”€ Controllers/ β”‚ └── ApiController.cs └── Program.cs ``` #### Rust Module File Structure ``` my_app/ β”œβ”€β”€ Cargo.toml └── src/ β”œβ”€β”€ main.rs (or lib.rs) β”œβ”€β”€ models/ β”‚ β”œβ”€β”€ mod.rs // Module declaration β”‚ β”œβ”€β”€ user.rs β”‚ └── product.rs β”œβ”€β”€ services/ β”‚ β”œβ”€β”€ mod.rs // Module declaration β”‚ β”œβ”€β”€ user_service.rs β”‚ └── product_service.rs └── controllers/ β”œβ”€β”€ mod.rs └── api_controller.rs ``` #### Module Declaration Patterns ```rust // src/models/mod.rs pub mod user; // Declares user.rs as a submodule pub mod product; // Declares product.rs as a submodule // Re-export commonly used types pub use user::User; pub use product::Product; // src/main.rs mod models; // Declares models/ as a module mod services; // Declares services/ as a module // Import specific items use models::{User, Product}; use services::UserService; // Or import the entire module use models::user::*; // Import all public items from user module ``` *** ## Crates vs .NET Assemblies ### Understanding Crates In Rust, a **crate** is the fundamental unit of compilation and code distribution, similar to how an **assembly** works in .NET. #### C# Assembly Model ```csharp // MyLibrary.dll - Compiled assembly namespace MyLibrary { public class Calculator { public int Add(int a, int b) => a + b; } } // MyApp.exe - Executable assembly that references MyLibrary.dll using MyLibrary; class Program { static void Main() { var calc = new Calculator(); Console.WriteLine(calc.Add(2, 3)); } } ``` #### Rust Crate Model ```toml # Cargo.toml for library crate [package] name = "my_calculator" version = "0.1.0" edition = "2021" [lib] name = "my_calculator" ``` ```rust // src/lib.rs - Library crate pub struct Calculator; impl Calculator { pub fn add(&self, a: i32, b: i32) -> i32 { a + b } } ``` ```toml # Cargo.toml for binary crate that uses the library [package] name = "my_app" version = "0.1.0" edition = "2021" [dependencies] my_calculator = { path = "../my_calculator" } ``` ```rust // src/main.rs - Binary crate use my_calculator::Calculator; fn main() { let calc = Calculator; println!("{}", calc.add(2, 3)); } ``` ### Crate Types Comparison | C# Concept | Rust Equivalent | Purpose | |------------|----------------|---------| | Class Library (.dll) | Library crate | Reusable code | | Console App (.exe) | Binary crate | Executable program | | NuGet Package | Published crate | Distribution unit | | Assembly (.dll/.exe) | Compiled crate | Compilation unit | | Solution (.sln) | Workspace | Multi-project organization | ### Workspace vs Solution #### C# Solution Structure ```xml ``` #### Rust Workspace Structure ```toml # Cargo.toml at workspace root [workspace] members = [ "web_api", "business", "data_access", "tests" ] [workspace.dependencies] serde = "1.0" # Shared dependency versions tokio = "1.0" ``` ```toml # web_api/Cargo.toml [package] name = "web_api" version = "0.1.0" edition = "2021" [dependencies] business = { path = "../business" } serde = { workspace = true } # Use workspace version tokio = { workspace = true } ``` *** ## Package Management: Cargo vs NuGet ### Dependency Declaration #### C# NuGet Dependencies ```xml net8.0 ``` #### Rust Cargo Dependencies ```toml # Cargo.toml [package] name = "my_app" version = "0.1.0" edition = "2021" [dependencies] serde_json = "1.0" # From crates.io (like NuGet) serde = { version = "1.0", features = ["derive"] } # With features log = "0.4" tokio = { version = "1.0", features = ["full"] } # Local dependencies (like ProjectReference) my_library = { path = "../my_library" } # Git dependencies my_git_crate = { git = "https://github.com/user/repo" } # Development dependencies (like test packages) [dev-dependencies] criterion = "0.5" # Benchmarking proptest = "1.0" # Property testing ``` ### Version Management #### C# Package Versioning ```xml true ``` #### Rust Version Management ```toml # Cargo.toml - Semantic versioning [dependencies] serde = "1.0" # Compatible with 1.x.x (>=1.0.0, <2.0.0) log = "0.4.17" # Compatible with 0.4.x (>=0.4.17, <0.5.0) regex = "=1.5.4" # Exact version chrono = "^0.4" # Caret requirements (default) uuid = "~1.3.0" # Tilde requirements (>=1.3.0, <1.4.0) # Cargo.lock - Exact versions for reproducible builds (auto-generated) [[package]] name = "serde" version = "1.0.163" # ... exact dependency tree ``` ### Package Sources #### C# Package Sources ```xml ``` #### Rust Package Sources ```toml # .cargo/config.toml [source.crates-io] replace-with = "my-awesome-registry" [source.my-awesome-registry] registry = "https://my-intranet:8080/index" # Alternative registries [registries] my-registry = { index = "https://my-intranet:8080/index" } # In Cargo.toml [dependencies] my_crate = { version = "1.0", registry = "my-registry" } ``` ### Common Commands Comparison | Task | C# Command | Rust Command | |------|------------|-------------| | Restore packages | `dotnet restore` | `cargo fetch` | | Add package | `dotnet add package Newtonsoft.Json` | `cargo add serde_json` | | Remove package | `dotnet remove package Newtonsoft.Json` | `cargo remove serde_json` | | Update packages | `dotnet update` | `cargo update` | | List packages | `dotnet list package` | `cargo tree` | | Audit security | `dotnet list package --vulnerable` | `cargo audit` | | Clean build | `dotnet clean` | `cargo clean` | ### Features: Conditional Compilation #### C# Conditional Compilation ```csharp #if DEBUG Console.WriteLine("Debug mode"); #elif RELEASE Console.WriteLine("Release mode"); #endif // Project file features DEBUG;TRACE ``` #### Rust Feature Gates ```toml # Cargo.toml [features] default = ["json"] # Default features json = ["serde_json"] # Feature that enables serde_json xml = ["serde_xml"] # Alternative serialization advanced = ["json", "xml"] # Composite feature [dependencies] serde_json = { version = "1.0", optional = true } serde_xml = { version = "0.4", optional = true } ``` ```rust // Conditional compilation based on features #[cfg(feature = "json")] use serde_json; #[cfg(feature = "xml")] use serde_xml; pub fn serialize_data(data: &MyStruct) -> String { #[cfg(feature = "json")] return serde_json::to_string(data).unwrap(); #[cfg(feature = "xml")] return serde_xml::to_string(data).unwrap(); #[cfg(not(any(feature = "json", feature = "xml")))] return "No serialization feature enabled".to_string(); } ``` ### Using External Crates #### Popular Crates for C# Developers | C# Library | Rust Crate | Purpose | |------------|------------|---------| | Newtonsoft.Json | `serde_json` | JSON serialization | | HttpClient | `reqwest` | HTTP client | | Entity Framework | `diesel` / `sqlx` | ORM / SQL toolkit | | NLog/Serilog | `log` + `env_logger` | Logging | | xUnit/NUnit | Built-in `#[test]` | Unit testing | | Moq | `mockall` | Mocking | | Flurl | `url` | URL manipulation | | Polly | `tower` | Resilience patterns | #### Example: HTTP Client Migration ```csharp // C# HttpClient usage public class ApiClient { private readonly HttpClient _httpClient; public async Task GetUserAsync(int id) { var response = await _httpClient.GetAsync($"/users/{id}"); var json = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject(json); } } ``` ```rust // Rust reqwest usage use reqwest; use serde::Deserialize; #[derive(Deserialize)] struct User { id: u32, name: String, } struct ApiClient { client: reqwest::Client, } impl ApiClient { async fn get_user(&self, id: u32) -> Result { let user = self.client .get(&format!("https://api.example.com/users/{}", id)) .send() .await? .json::() .await?; Ok(user) } } ``` *** ## Traits - Rust's Interfaces Traits are Rust's way of defining shared behavior, similar to interfaces in C# but more powerful. ### C# Interface Comparison ```csharp // C# interface definition public interface IAnimal { string Name { get; } void MakeSound(); // Default implementation (C# 8+) string Describe() { return $"{Name} makes a sound"; } } // C# interface implementation public class Dog : IAnimal { public string Name { get; } public Dog(string name) { Name = name; } public void MakeSound() { Console.WriteLine("Woof!"); } // Can override default implementation public string Describe() { return $"{Name} is a loyal dog"; } } // Generic constraints public void ProcessAnimal(T animal) where T : IAnimal { animal.MakeSound(); Console.WriteLine(animal.Describe()); } ``` ### Rust Trait Definition and Implementation ```rust // Trait definition trait Animal { fn name(&self) -> &str; fn make_sound(&self); // Default implementation fn describe(&self) -> String { format!("{} makes a sound", self.name()) } // Default implementation using other trait methods fn introduce(&self) { println!("Hi, I'm {}", self.name()); self.make_sound(); } } // Struct definition #[derive(Debug)] struct Dog { name: String, breed: String, } impl Dog { fn new(name: String, breed: String) -> Dog { Dog { name, breed } } } // Trait implementation impl Animal for Dog { fn name(&self) -> &str { &self.name } fn make_sound(&self) { println!("Woof!"); } // Override default implementation fn describe(&self) -> String { format!("{} is a loyal {} dog", self.name, self.breed) } } // Another implementation #[derive(Debug)] struct Cat { name: String, indoor: bool, } impl Animal for Cat { fn name(&self) -> &str { &self.name } fn make_sound(&self) { println!("Meow!"); } // Use default describe() implementation } // Generic function with trait bounds fn process_animal(animal: &T) { animal.make_sound(); println!("{}", animal.describe()); animal.introduce(); } // Multiple trait bounds fn process_animal_debug(animal: &T) { println!("Debug: {:?}", animal); process_animal(animal); } fn main() { let dog = Dog::new("Buddy".to_string(), "Golden Retriever".to_string()); let cat = Cat { name: "Whiskers".to_string(), indoor: true }; process_animal(&dog); process_animal(&cat); process_animal_debug(&dog); } ``` ### Trait Objects and Dynamic Dispatch ```csharp // C# dynamic polymorphism public void ProcessAnimals(List animals) { foreach (var animal in animals) { animal.MakeSound(); // Dynamic dispatch Console.WriteLine(animal.Describe()); } } // Usage var animals = new List { new Dog("Buddy"), new Cat("Whiskers"), new Dog("Rex") }; ProcessAnimals(animals); ``` ```rust // Rust trait objects for dynamic dispatch fn process_animals(animals: &[Box]) { for animal in animals { animal.make_sound(); // Dynamic dispatch println!("{}", animal.describe()); } } // Alternative: using references fn process_animal_refs(animals: &[&dyn Animal]) { for animal in animals { animal.make_sound(); println!("{}", animal.describe()); } } fn main() { // Using Box let animals: Vec> = vec![ Box::new(Dog::new("Buddy".to_string(), "Golden Retriever".to_string())), Box::new(Cat { name: "Whiskers".to_string(), indoor: true }), Box::new(Dog::new("Rex".to_string(), "German Shepherd".to_string())), ]; process_animals(&animals); // Using references let dog = Dog::new("Buddy".to_string(), "Golden Retriever".to_string()); let cat = Cat { name: "Whiskers".to_string(), indoor: true }; let animal_refs: Vec<&dyn Animal> = vec![&dog, &cat]; process_animal_refs(&animal_refs); } ``` ### Derived Traits ```rust // Automatically derive common traits #[derive(Debug, Clone, PartialEq, Eq, Hash)] struct Person { name: String, age: u32, } // What this generates (simplified): impl std::fmt::Debug for Person { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Person") .field("name", &self.name) .field("age", &self.age) .finish() } } impl Clone for Person { fn clone(&self) -> Self { Person { name: self.name.clone(), age: self.age, } } } impl PartialEq for Person { fn eq(&self, other: &Self) -> bool { self.name == other.name && self.age == other.age } } // Usage fn main() { let person1 = Person { name: "Alice".to_string(), age: 30, }; let person2 = person1.clone(); // Clone trait println!("{:?}", person1); // Debug trait println!("Equal: {}", person1 == person2); // PartialEq trait } ``` ### Common Standard Library Traits ```rust use std::collections::HashMap; // Display trait for user-friendly output impl std::fmt::Display for Person { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{} (age {})", self.name, self.age) } } // From trait for conversions impl From<(String, u32)> for Person { fn from((name, age): (String, u32)) -> Self { Person { name, age } } } // Into trait is automatically implemented when From is implemented fn create_person() { let person: Person = ("Alice".to_string(), 30).into(); println!("{}", person); } // Iterator trait implementation struct PersonIterator { people: Vec, index: usize, } impl Iterator for PersonIterator { type Item = Person; fn next(&mut self) -> Option { if self.index < self.people.len() { let person = self.people[self.index].clone(); self.index += 1; Some(person) } else { None } } } impl Person { fn iterator(people: Vec) -> PersonIterator { PersonIterator { people, index: 0 } } } fn main() { let people = vec![ Person::from(("Alice".to_string(), 30)), Person::from(("Bob".to_string(), 25)), Person::from(("Charlie".to_string(), 35)), ]; // Use our custom iterator for person in Person::iterator(people.clone()) { println!("{}", person); // Uses Display trait } } ``` *** ## Error Handling Deep Dive ### C# Exception Model ```csharp public class FileProcessor { public string ProcessFile(string path) { try { var content = File.ReadAllText(path); if (string.IsNullOrEmpty(content)) throw new InvalidOperationException("File is empty"); return content.ToUpper(); } catch (FileNotFoundException) { throw new ApplicationException($"File not found: {path}"); } catch (UnauthorizedAccessException) { throw new ApplicationException($"Access denied: {path}"); } catch (Exception ex) { throw new ApplicationException($"Unexpected error: {ex.Message}"); } } public async Task> ProcessMultipleFiles(List paths) { var results = new List(); foreach (var path in paths) { try { var result = ProcessFile(path); results.Add(result); } catch (Exception ex) { // Log error but continue with other files Console.WriteLine($"Error processing {path}: {ex.Message}"); } } return results; } } ``` ### Rust Result-Based Error Handling ```rust use std::fs; use std::io; #[derive(Debug)] enum ProcessingError { FileNotFound(String), AccessDenied(String), EmptyFile(String), IoError(io::Error), } impl std::fmt::Display for ProcessingError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ProcessingError::FileNotFound(path) => write!(f, "File not found: {}", path), ProcessingError::AccessDenied(path) => write!(f, "Access denied: {}", path), ProcessingError::EmptyFile(path) => write!(f, "File is empty: {}", path), ProcessingError::IoError(err) => write!(f, "IO error: {}", err), } } } impl std::error::Error for ProcessingError {} impl From for ProcessingError { fn from(error: io::Error) -> Self { ProcessingError::IoError(error) } } struct FileProcessor; impl FileProcessor { fn process_file(path: &str) -> Result { // Use ? operator for early returns let content = fs::read_to_string(path) .map_err(|err| match err.kind() { io::ErrorKind::NotFound => ProcessingError::FileNotFound(path.to_string()), io::ErrorKind::PermissionDenied => ProcessingError::AccessDenied(path.to_string()), _ => ProcessingError::IoError(err), })?; if content.is_empty() { return Err(ProcessingError::EmptyFile(path.to_string())); } Ok(content.to_uppercase()) } fn process_multiple_files(paths: &[&str]) -> Vec> { paths.iter() .map(|&path| Self::process_file(path)) .collect() } // Alternative: collect only successful results fn process_multiple_files_successful(paths: &[&str]) -> (Vec, Vec) { let results: Vec<_> = Self::process_multiple_files(paths); let mut successes = Vec::new(); let mut errors = Vec::new(); for result in results { match result { Ok(content) => successes.push(content), Err(error) => { eprintln!("Error: {}", error); errors.push(error); } } } (successes, errors) } } fn main() { let paths = vec!["file1.txt", "file2.txt", "nonexistent.txt"]; // Process individual file match FileProcessor::process_file("example.txt") { Ok(content) => println!("Content: {}", content), Err(error) => eprintln!("Error: {}", error), } // Process multiple files - keep all results let results = FileProcessor::process_multiple_files(&paths); for (i, result) in results.iter().enumerate() { match result { Ok(content) => println!("File {}: Success", i), Err(error) => println!("File {}: Error - {}", i, error), } } // Process multiple files - separate successes and errors let (successes, errors) = FileProcessor::process_multiple_files_successful(&paths); println!("Processed {} files successfully, {} errors", successes.len(), errors.len()); } ``` *** ## Practical Migration Examples Let's look at some real-world scenarios showing how common C# patterns translate to Rust. ### Configuration Management ```csharp // C# configuration class public class AppConfig { public string DatabaseUrl { get; set; } = "localhost"; public int Port { get; set; } = 5432; public List AllowedHosts { get; set; } = new(); public Dictionary FeatureFlags { get; set; } = new(); public static AppConfig LoadFromFile(string path) { try { var json = File.ReadAllText(path); return JsonSerializer.Deserialize(json) ?? new AppConfig(); } catch (Exception ex) { Console.WriteLine($"Failed to load config: {ex.Message}"); return new AppConfig(); // Fall back to defaults } } public void Validate() { if (string.IsNullOrEmpty(DatabaseUrl)) throw new InvalidOperationException("DatabaseUrl is required"); if (Port <= 0 || Port > 65535) throw new InvalidOperationException("Port must be between 1 and 65535"); } } ``` ```rust use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fs; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct AppConfig { pub database_url: String, pub port: u16, pub allowed_hosts: Vec, pub feature_flags: HashMap, } #[derive(Debug)] pub enum ConfigError { FileNotFound(String), ParseError(String), ValidationError(String), } impl std::fmt::Display for ConfigError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ConfigError::FileNotFound(path) => write!(f, "Config file not found: {}", path), ConfigError::ParseError(msg) => write!(f, "Failed to parse config: {}", msg), ConfigError::ValidationError(msg) => write!(f, "Invalid config: {}", msg), } } } impl std::error::Error for ConfigError {} impl Default for AppConfig { fn default() -> Self { AppConfig { database_url: "localhost".to_string(), port: 5432, allowed_hosts: Vec::new(), feature_flags: HashMap::new(), } } } impl AppConfig { pub fn load_from_file(path: &str) -> Result { let contents = fs::read_to_string(path) .map_err(|_| ConfigError::FileNotFound(path.to_string()))?; let config: AppConfig = serde_json::from_str(&contents) .map_err(|e| ConfigError::ParseError(e.to_string()))?; config.validate()?; Ok(config) } pub fn load_or_default(path: &str) -> AppConfig { Self::load_from_file(path) .unwrap_or_else(|error| { eprintln!("Failed to load config: {}", error); AppConfig::default() }) } pub fn validate(&self) -> Result<(), ConfigError> { if self.database_url.is_empty() { return Err(ConfigError::ValidationError("DatabaseUrl is required".to_string())); } if self.port == 0 { return Err(ConfigError::ValidationError("Port must be greater than 0".to_string())); } Ok(()) } pub fn get_feature_flag(&self, key: &str) -> Option<&String> { self.feature_flags.get(key) } pub fn is_feature_enabled(&self, key: &str) -> bool { self.get_feature_flag(key) .map(|value| value.to_lowercase() == "true") .unwrap_or(false) } } fn main() -> Result<(), Box> { // Try to load config, fall back to defaults let config = AppConfig::load_or_default("config.json"); println!("Config: {:?}", config); // Check feature flags if config.is_feature_enabled("debug_mode") { println!("Debug mode is enabled"); } Ok(()) } ``` ### Data Processing Pipeline ```csharp // C# data processing public class DataProcessor { public async Task> ProcessAsync(List data) { var results = new List(); foreach (var item in data) { try { if (IsValid(item)) { var processed = await TransformAsync(item); results.Add(processed); } } catch (Exception ex) { Console.WriteLine($"Error processing item {item.Id}: {ex.Message}"); } } return results; } private bool IsValid(RawData data) { return !string.IsNullOrEmpty(data.Value) && data.Timestamp > DateTime.MinValue; } private async Task TransformAsync(RawData data) { // Simulate async processing await Task.Delay(10); return new ProcessedData { Id = data.Id, ProcessedValue = data.Value.ToUpper(), ProcessedAt = DateTime.UtcNow }; } } public class RawData { public int Id { get; set; } public string Value { get; set; } = ""; public DateTime Timestamp { get; set; } } public class ProcessedData { public int Id { get; set; } public string ProcessedValue { get; set; } = ""; public DateTime ProcessedAt { get; set; } } ``` ```rust use std::time::{SystemTime, UNIX_EPOCH}; use tokio; #[derive(Debug, Clone)] pub struct RawData { pub id: u32, pub value: String, pub timestamp: u64, } #[derive(Debug)] pub struct ProcessedData { pub id: u32, pub processed_value: String, pub processed_at: u64, } #[derive(Debug)] pub enum ProcessingError { InvalidData(String), TransformationFailed(String), } impl std::fmt::Display for ProcessingError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ProcessingError::InvalidData(msg) => write!(f, "Invalid data: {}", msg), ProcessingError::TransformationFailed(msg) => write!(f, "Transformation failed: {}", msg), } } } impl std::error::Error for ProcessingError {} pub struct DataProcessor; impl DataProcessor { pub async fn process(data: Vec) -> Vec> { // Use futures for concurrent processing let futures = data.into_iter().map(|item| async move { Self::validate(&item)?; Self::transform(item).await }); // Collect all futures futures::future::join_all(futures).await } pub async fn process_successful_only(data: Vec) -> Vec { let results = Self::process(data).await; results.into_iter() .filter_map(|result| match result { Ok(processed) => Some(processed), Err(error) => { eprintln!("Processing error: {}", error); None } }) .collect() } fn validate(data: &RawData) -> Result<(), ProcessingError> { if data.value.is_empty() { return Err(ProcessingError::InvalidData("Value cannot be empty".to_string())); } if data.timestamp == 0 { return Err(ProcessingError::InvalidData("Invalid timestamp".to_string())); } Ok(()) } async fn transform(data: RawData) -> Result { // Simulate async processing tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; let processed_value = data.value.to_uppercase(); if processed_value.len() > 1000 { return Err(ProcessingError::TransformationFailed("Processed value too long".to_string())); } let processed_at = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_secs(); Ok(ProcessedData { id: data.id, processed_value, processed_at, }) } } #[tokio::main] async fn main() -> Result<(), Box> { let raw_data = vec![ RawData { id: 1, value: "hello".to_string(), timestamp: 1234567890 }, RawData { id: 2, value: "world".to_string(), timestamp: 1234567891 }, RawData { id: 3, value: "".to_string(), timestamp: 1234567892 }, // Invalid ]; // Process and handle errors explicitly let results = DataProcessor::process(raw_data.clone()).await; for (i, result) in results.iter().enumerate() { match result { Ok(processed) => println!("Item {}: {:?}", i, processed), Err(error) => println!("Item {}: Error - {}", i, error), } } // Process and keep only successful results let successful = DataProcessor::process_successful_only(raw_data).await; println!("Successfully processed {} items", successful.len()); Ok(()) } ``` ### HTTP Client Example ```csharp // C# HTTP client public class ApiClient { private readonly HttpClient _httpClient; public ApiClient(HttpClient httpClient) { _httpClient = httpClient; } public async Task GetAsync(string endpoint) where T : class { try { var response = await _httpClient.GetAsync(endpoint); if (response.IsSuccessStatusCode) { var json = await response.Content.ReadAsStringAsync(); return JsonSerializer.Deserialize(json); } Console.WriteLine($"HTTP Error: {response.StatusCode}"); return null; } catch (Exception ex) { Console.WriteLine($"Request failed: {ex.Message}"); return null; } } public async Task PostAsync(string endpoint, T data) { try { var json = JsonSerializer.Serialize(data); var content = new StringContent(json, Encoding.UTF8, "application/json"); var response = await _httpClient.PostAsync(endpoint, content); return response.IsSuccessStatusCode; } catch (Exception ex) { Console.WriteLine($"POST failed: {ex.Message}"); return false; } } } ``` ```rust use reqwest; use serde::{Deserialize, Serialize}; #[derive(Debug)] pub enum ApiError { NetworkError(reqwest::Error), HttpError(u16, String), ParseError(String), } impl std::fmt::Display for ApiError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ApiError::NetworkError(err) => write!(f, "Network error: {}", err), ApiError::HttpError(code, msg) => write!(f, "HTTP {} error: {}", code, msg), ApiError::ParseError(msg) => write!(f, "Parse error: {}", msg), } } } impl std::error::Error for ApiError {} impl From for ApiError { fn from(error: reqwest::Error) -> Self { ApiError::NetworkError(error) } } pub struct ApiClient { client: reqwest::Client, base_url: String, } impl ApiClient { pub fn new(base_url: String) -> Self { ApiClient { client: reqwest::Client::new(), base_url, } } pub async fn get(&self, endpoint: &str) -> Result where T: for<'de> Deserialize<'de>, { let url = format!("{}/{}", self.base_url, endpoint); let response = self.client.get(&url).send().await?; if response.status().is_success() { let data = response.json::().await .map_err(|e| ApiError::ParseError(e.to_string()))?; Ok(data) } else { let status = response.status().as_u16(); let body = response.text().await.unwrap_or_default(); Err(ApiError::HttpError(status, body)) } } pub async fn post(&self, endpoint: &str, data: &T) -> Result where T: Serialize, R: for<'de> Deserialize<'de>, { let url = format!("{}/{}", self.base_url, endpoint); let response = self.client .post(&url) .json(data) .send() .await?; if response.status().is_success() { let result = response.json::().await .map_err(|e| ApiError::ParseError(e.to_string()))?; Ok(result) } else { let status = response.status().as_u16(); let body = response.text().await.unwrap_or_default(); Err(ApiError::HttpError(status, body)) } } } #[derive(Serialize, Deserialize, Debug)] struct User { id: u32, name: String, email: String, } #[derive(Serialize, Debug)] struct CreateUserRequest { name: String, email: String, } #[tokio::main] async fn main() -> Result<(), Box> { let client = ApiClient::new("https://jsonplaceholder.typicode.com".to_string()); // GET request match client.get::("users/1").await { Ok(user) => println!("User: {:?}", user), Err(error) => eprintln!("Failed to get user: {}", error), } // POST request let new_user = CreateUserRequest { name: "John Doe".to_string(), email: "john@example.com".to_string(), }; match client.post::("users", &new_user).await { Ok(created_user) => println!("Created user: {:?}", created_user), Err(error) => eprintln!("Failed to create user: {}", error), } Ok(()) } ``` *** ## Learning Path and Next Steps ### Immediate Next Steps (Week 1-2) 1. **Set up your environment** - Install Rust via [rustup.rs](https://rustup.rs/) - Configure VS Code with rust-analyzer extension - Create your first `cargo new hello_world` project 2. **Master the basics** - Practice ownership with simple exercises - Write functions with different parameter types (`&str`, `String`, `&mut`) - Implement basic structs and methods 3. **Error handling practice** - Convert C# try-catch code to Result-based patterns - Practice with `?` operator and `match` statements - Implement custom error types ### Intermediate Goals (Month 1-2) 1. **Collections and iterators** - Master `Vec`, `HashMap`, and `HashSet` - Learn iterator methods: `map`, `filter`, `collect`, `fold` - Practice with `for` loops vs iterator chains 2. **Traits and generics** - Implement common traits: `Debug`, `Clone`, `PartialEq` - Write generic functions and structs - Understand trait bounds and where clauses 3. **Project structure** - Organize code into modules - Understand `pub` visibility - Work with external crates from crates.io ### Advanced Topics (Month 3+) 1. **Concurrency** - Learn about `Send` and `Sync` traits - Use `std::thread` for basic parallelism - Explore `tokio` for async programming 2. **Memory management** - Understand `Rc` and `Arc` for shared ownership - Learn when to use `Box` for heap allocation - Master lifetimes for complex scenarios 3. **Real-world projects** - Build a CLI tool with `clap` - Create a web API with `axum` or `warp` - Write a library and publish to crates.io ### Recommended Learning Resources #### Books - **"The Rust Programming Language"** (free online) - The official book - **"Rust by Example"** (free online) - Hands-on examples - **"Programming Rust"** by Jim Blandy - Deep technical coverage #### Online Resources - [Rust Playground](https://play.rust-lang.org/) - Try code in browser - [Rustlings](https://github.com/rust-lang/rustlings) - Interactive exercises - [Rust by Example](https://doc.rust-lang.org/rust-by-example/) - Practical examples #### Practice Projects 1. **Command-line calculator** - Practice with enums and pattern matching 2. **File organizer** - Work with filesystem and error handling 3. **JSON processor** - Learn serde and data transformation 4. **HTTP server** - Understand async programming and networking 5. **Database library** - Master traits, generics, and error handling ### Common Pitfalls for C# Developers #### Ownership Confusion ```rust // DON'T: Trying to use moved values fn wrong_way() { let s = String::from("hello"); takes_ownership(s); // println!("{}", s); // ERROR: s was moved } // DO: Use references or clone when needed fn right_way() { let s = String::from("hello"); borrows_string(&s); println!("{}", s); // OK: s is still owned here } fn takes_ownership(s: String) { /* s is moved here */ } fn borrows_string(s: &str) { /* s is borrowed here */ } ``` #### Fighting the Borrow Checker ```rust // DON'T: Multiple mutable references fn wrong_borrowing() { let mut v = vec![1, 2, 3]; let r1 = &mut v; // let r2 = &mut v; // ERROR: cannot borrow as mutable more than once } // DO: Limit scope of mutable borrows fn right_borrowing() { let mut v = vec![1, 2, 3]; { let r1 = &mut v; r1.push(4); } // r1 goes out of scope here let r2 = &mut v; // OK: no other mutable borrows exist r2.push(5); } ``` #### Expecting Null Values ```rust // DON'T: Expecting null-like behavior fn no_null_in_rust() { // let s: String = null; // NO null in Rust! } // DO: Use Option explicitly fn use_option_instead() { let maybe_string: Option = None; match maybe_string { Some(s) => println!("Got string: {}", s), None => println!("No string available"), } } ``` ### Final Tips 1. **Embrace the compiler** - Rust's compiler errors are helpful, not hostile 2. **Start small** - Begin with simple programs and gradually add complexity 3. **Read other people's code** - Study popular crates on GitHub 4. **Ask for help** - The Rust community is welcoming and helpful 5. **Practice regularly** - Rust's concepts become natural with practice Remember: Rust has a learning curve, but it pays off with memory safety, performance, and fearless concurrency. The ownership system that seems restrictive at first becomes a powerful tool for writing correct, efficient programs. --- **Congratulations!** You now have a solid foundation for transitioning from C# to Rust. Start with simple projects, be patient with the learning process, and gradually work your way up to more complex applications. The safety and performance benefits of Rust make the initial learning investment worthwhile. For the next phase of your learning journey, consider diving deeper into the [Advanced Rust Training for C# Programmers](./RustTrainingForCSharp.md) guide, which covers more sophisticated patterns, performance optimization, and real-world application architecture.