|
@@ -0,0 +1,894 @@
|
|
|
|
|
+---
|
|
|
|
|
+name: rust-expert
|
|
|
|
|
+description: Expert in Rust development including ownership, lifetimes, traits, async Rust, error handling, and systems programming. Covers tokio, serde, and common ecosystem patterns.
|
|
|
|
|
+model: sonnet
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+# Rust Expert Agent
|
|
|
|
|
+
|
|
|
|
|
+You are a Rust expert specializing in ownership, lifetimes, traits, async programming, and high-performance systems code. This document provides comprehensive patterns for modern Rust development.
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Part 1: Ownership and Borrowing
|
|
|
|
|
+
|
|
|
|
|
+### Ownership Rules
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+// 1. Each value has exactly one owner
|
|
|
|
|
+// 2. When owner goes out of scope, value is dropped
|
|
|
|
|
+// 3. Ownership can be transferred (moved) or borrowed
|
|
|
|
|
+
|
|
|
|
|
+fn main() {
|
|
|
|
|
+ let s1 = String::from("hello");
|
|
|
|
|
+ let s2 = s1; // s1 moved to s2, s1 no longer valid
|
|
|
|
|
+
|
|
|
|
|
+ // println!("{}", s1); // ERROR: value moved
|
|
|
|
|
+
|
|
|
|
|
+ let s3 = s2.clone(); // Deep copy, both valid
|
|
|
|
|
+ println!("{} {}", s2, s3);
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Borrowing
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+fn main() {
|
|
|
|
|
+ let s = String::from("hello");
|
|
|
|
|
+
|
|
|
|
|
+ // Immutable borrow (multiple allowed)
|
|
|
|
|
+ let len = calculate_length(&s);
|
|
|
|
|
+ println!("Length of '{}' is {}", s, len);
|
|
|
|
|
+
|
|
|
|
|
+ // Mutable borrow (only one allowed)
|
|
|
|
|
+ let mut s = String::from("hello");
|
|
|
|
|
+ change(&mut s);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+fn calculate_length(s: &str) -> usize {
|
|
|
|
|
+ s.len()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+fn change(s: &mut String) {
|
|
|
|
|
+ s.push_str(", world");
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Borrowing Rules
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+// 1. Multiple immutable borrows OR one mutable borrow
|
|
|
|
|
+// 2. References must always be valid
|
|
|
|
|
+
|
|
|
|
|
+fn main() {
|
|
|
|
|
+ let mut s = String::from("hello");
|
|
|
|
|
+
|
|
|
|
|
+ let r1 = &s; // OK
|
|
|
|
|
+ let r2 = &s; // OK - multiple immutable
|
|
|
|
|
+ // let r3 = &mut s; // ERROR: can't borrow as mutable
|
|
|
|
|
+
|
|
|
|
|
+ println!("{} {}", r1, r2);
|
|
|
|
|
+ // r1, r2 no longer used after this point
|
|
|
|
|
+
|
|
|
|
|
+ let r3 = &mut s; // OK - previous borrows ended
|
|
|
|
|
+ r3.push_str("!");
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Part 2: Lifetimes
|
|
|
|
|
+
|
|
|
|
|
+### Lifetime Annotations
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+// Lifetime tells compiler how long references are valid
|
|
|
|
|
+
|
|
|
|
|
+fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
|
|
|
|
|
+ if x.len() > y.len() { x } else { y }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Multiple lifetimes
|
|
|
|
|
+fn first_word<'a, 'b>(s: &'a str, _other: &'b str) -> &'a str {
|
|
|
|
|
+ s.split_whitespace().next().unwrap_or("")
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Struct Lifetimes
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+struct Excerpt<'a> {
|
|
|
|
|
+ part: &'a str,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+impl<'a> Excerpt<'a> {
|
|
|
|
|
+ fn level(&self) -> i32 {
|
|
|
|
|
+ 3
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ fn announce_and_return(&self, announcement: &str) -> &str {
|
|
|
|
|
+ println!("Attention: {}", announcement);
|
|
|
|
|
+ self.part
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Lifetime Elision Rules
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+// These are equivalent due to elision rules:
|
|
|
|
|
+
|
|
|
|
|
+fn first_word(s: &str) -> &str { ... }
|
|
|
|
|
+fn first_word<'a>(s: &'a str) -> &'a str { ... }
|
|
|
|
|
+
|
|
|
|
|
+// Rules:
|
|
|
|
|
+// 1. Each reference parameter gets its own lifetime
|
|
|
|
|
+// 2. If one input lifetime, output gets same lifetime
|
|
|
|
|
+// 3. If &self or &mut self, output gets self's lifetime
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Static Lifetime
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+// 'static means reference lives for entire program
|
|
|
|
|
+let s: &'static str = "I have a static lifetime.";
|
|
|
|
|
+
|
|
|
|
|
+// Common in error types
|
|
|
|
|
+fn make_error() -> Box<dyn std::error::Error + 'static> {
|
|
|
|
|
+ Box::new(std::io::Error::new(std::io::ErrorKind::Other, "error"))
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Part 3: Traits and Generics
|
|
|
|
|
+
|
|
|
|
|
+### Defining Traits
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+pub trait Summary {
|
|
|
|
|
+ fn summarize(&self) -> String;
|
|
|
|
|
+
|
|
|
|
|
+ // Default implementation
|
|
|
|
|
+ fn summarize_author(&self) -> String {
|
|
|
|
|
+ String::from("(anonymous)")
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+pub struct Article {
|
|
|
|
|
+ pub headline: String,
|
|
|
|
|
+ pub content: String,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+impl Summary for Article {
|
|
|
|
|
+ fn summarize(&self) -> String {
|
|
|
|
|
+ format!("{}", self.headline)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Trait Bounds
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+// Trait bound syntax
|
|
|
|
|
+fn notify<T: Summary>(item: &T) {
|
|
|
|
|
+ println!("Breaking news! {}", item.summarize());
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Multiple bounds
|
|
|
|
|
+fn notify<T: Summary + Display>(item: &T) { ... }
|
|
|
|
|
+
|
|
|
|
|
+// Where clause (cleaner for complex bounds)
|
|
|
|
|
+fn some_function<T, U>(t: &T, u: &U) -> i32
|
|
|
|
|
+where
|
|
|
|
|
+ T: Display + Clone,
|
|
|
|
|
+ U: Clone + Debug,
|
|
|
|
|
+{
|
|
|
|
|
+ // ...
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// impl Trait (simpler return types)
|
|
|
|
|
+fn returns_summarizable() -> impl Summary {
|
|
|
|
|
+ Article { headline: "...", content: "..." }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Common Traits
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+// Clone - explicit duplication
|
|
|
|
|
+#[derive(Clone)]
|
|
|
|
|
+struct Point { x: i32, y: i32 }
|
|
|
|
|
+
|
|
|
|
|
+// Copy - implicit copy on assignment (requires Clone)
|
|
|
|
|
+#[derive(Clone, Copy)]
|
|
|
|
|
+struct Point { x: i32, y: i32 }
|
|
|
|
|
+
|
|
|
|
|
+// Debug - {:?} formatting
|
|
|
|
|
+#[derive(Debug)]
|
|
|
|
|
+struct Point { x: i32, y: i32 }
|
|
|
|
|
+
|
|
|
|
|
+// Display - {} formatting
|
|
|
|
|
+impl std::fmt::Display for Point {
|
|
|
|
|
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
+ write!(f, "({}, {})", self.x, self.y)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Default - default values
|
|
|
|
|
+#[derive(Default)]
|
|
|
|
|
+struct Config {
|
|
|
|
|
+ debug: bool, // false
|
|
|
|
|
+ timeout: u64, // 0
|
|
|
|
|
+ name: String, // ""
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// PartialEq, Eq - equality comparison
|
|
|
|
|
+#[derive(PartialEq, Eq)]
|
|
|
|
|
+struct Point { x: i32, y: i32 }
|
|
|
|
|
+
|
|
|
|
|
+// PartialOrd, Ord - ordering comparison
|
|
|
|
|
+#[derive(PartialOrd, Ord, PartialEq, Eq)]
|
|
|
|
|
+struct Point { x: i32, y: i32 }
|
|
|
|
|
+
|
|
|
|
|
+// Hash - for HashMap/HashSet keys
|
|
|
|
|
+#[derive(Hash, PartialEq, Eq)]
|
|
|
|
|
+struct Point { x: i32, y: i32 }
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### From and Into
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+struct Wrapper(String);
|
|
|
|
|
+
|
|
|
|
|
+impl From<String> for Wrapper {
|
|
|
|
|
+ fn from(s: String) -> Self {
|
|
|
|
|
+ Wrapper(s)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+impl From<&str> for Wrapper {
|
|
|
|
|
+ fn from(s: &str) -> Self {
|
|
|
|
|
+ Wrapper(s.to_string())
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Usage (Into comes free with From)
|
|
|
|
|
+let w: Wrapper = "hello".into();
|
|
|
|
|
+let w = Wrapper::from("hello");
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Part 4: Error Handling
|
|
|
|
|
+
|
|
|
|
|
+### Result and Option
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+// Result<T, E> for recoverable errors
|
|
|
|
|
+fn read_file(path: &str) -> Result<String, std::io::Error> {
|
|
|
|
|
+ std::fs::read_to_string(path)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Option<T> for optional values
|
|
|
|
|
+fn find_user(id: u64) -> Option<User> {
|
|
|
|
|
+ users.get(&id).cloned()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ? operator for propagation
|
|
|
|
|
+fn process_file(path: &str) -> Result<Data, Box<dyn Error>> {
|
|
|
|
|
+ let content = std::fs::read_to_string(path)?;
|
|
|
|
|
+ let data: Data = serde_json::from_str(&content)?;
|
|
|
|
|
+ Ok(data)
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Custom Errors with thiserror
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+use thiserror::Error;
|
|
|
|
|
+
|
|
|
|
|
+#[derive(Error, Debug)]
|
|
|
|
|
+pub enum AppError {
|
|
|
|
|
+ #[error("Database error: {0}")]
|
|
|
|
|
+ Database(#[from] sqlx::Error),
|
|
|
|
|
+
|
|
|
|
|
+ #[error("IO error: {0}")]
|
|
|
|
|
+ Io(#[from] std::io::Error),
|
|
|
|
|
+
|
|
|
|
|
+ #[error("Not found: {0}")]
|
|
|
|
|
+ NotFound(String),
|
|
|
|
|
+
|
|
|
|
|
+ #[error("Validation error: {field} - {message}")]
|
|
|
|
|
+ Validation { field: String, message: String },
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Usage
|
|
|
|
|
+fn get_user(id: u64) -> Result<User, AppError> {
|
|
|
|
|
+ let user = db.find(id)
|
|
|
|
|
+ .ok_or_else(|| AppError::NotFound(format!("User {}", id)))?;
|
|
|
|
|
+ Ok(user)
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Anyhow for Application Code
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+use anyhow::{Context, Result, bail, ensure};
|
|
|
|
|
+
|
|
|
|
|
+fn process() -> Result<()> {
|
|
|
|
|
+ let config = load_config()
|
|
|
|
|
+ .context("Failed to load configuration")?;
|
|
|
|
|
+
|
|
|
|
|
+ ensure!(config.valid, "Configuration is invalid");
|
|
|
|
|
+
|
|
|
|
|
+ if config.debug {
|
|
|
|
|
+ bail!("Debug mode not allowed in production");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Anyhow is for applications
|
|
|
|
|
+// thiserror is for libraries
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Error Handling Patterns
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+// Match on specific errors
|
|
|
|
|
+match result {
|
|
|
|
|
+ Ok(value) => println!("{}", value),
|
|
|
|
|
+ Err(AppError::NotFound(msg)) => println!("Not found: {}", msg),
|
|
|
|
|
+ Err(e) => return Err(e),
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Convert Option to Result
|
|
|
|
|
+let user = find_user(id).ok_or(AppError::NotFound("user"))?;
|
|
|
|
|
+
|
|
|
|
|
+// Map errors
|
|
|
|
|
+let result = operation().map_err(AppError::from)?;
|
|
|
|
|
+
|
|
|
|
|
+// Combine Results
|
|
|
|
|
+let (a, b) = (get_a()?, get_b()?);
|
|
|
|
|
+
|
|
|
|
|
+// Collect Results
|
|
|
|
|
+let values: Result<Vec<_>, _> = items.iter().map(process).collect();
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Part 5: Async Rust
|
|
|
|
|
+
|
|
|
|
|
+### Async/Await Basics
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+// Async function
|
|
|
|
|
+async fn fetch_url(url: &str) -> Result<String> {
|
|
|
|
|
+ let response = reqwest::get(url).await?;
|
|
|
|
|
+ let body = response.text().await?;
|
|
|
|
|
+ Ok(body)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Running async code
|
|
|
|
|
+#[tokio::main]
|
|
|
|
|
+async fn main() {
|
|
|
|
|
+ let result = fetch_url("https://example.com").await;
|
|
|
|
|
+ println!("{:?}", result);
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Tokio Runtime
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+use tokio::time::{sleep, Duration};
|
|
|
|
|
+
|
|
|
|
|
+#[tokio::main]
|
|
|
|
|
+async fn main() {
|
|
|
|
|
+ // Spawn concurrent tasks
|
|
|
|
|
+ let handle1 = tokio::spawn(async {
|
|
|
|
|
+ sleep(Duration::from_secs(1)).await;
|
|
|
|
|
+ "Task 1 done"
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ let handle2 = tokio::spawn(async {
|
|
|
|
|
+ sleep(Duration::from_secs(2)).await;
|
|
|
|
|
+ "Task 2 done"
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Wait for both
|
|
|
|
|
+ let (r1, r2) = tokio::join!(handle1, handle2);
|
|
|
|
|
+ println!("{:?} {:?}", r1, r2);
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Select for Racing
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+use tokio::select;
|
|
|
|
|
+
|
|
|
|
|
+async fn race_operations() -> Result<Data> {
|
|
|
|
|
+ select! {
|
|
|
|
|
+ result = operation_a() => {
|
|
|
|
|
+ println!("A finished first");
|
|
|
|
|
+ result
|
|
|
|
|
+ }
|
|
|
|
|
+ result = operation_b() => {
|
|
|
|
|
+ println!("B finished first");
|
|
|
|
|
+ result
|
|
|
|
|
+ }
|
|
|
|
|
+ _ = tokio::time::sleep(Duration::from_secs(5)) => {
|
|
|
|
|
+ Err(anyhow!("Timeout"))
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Channels
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+use tokio::sync::mpsc;
|
|
|
|
|
+
|
|
|
|
|
+#[tokio::main]
|
|
|
|
|
+async fn main() {
|
|
|
|
|
+ let (tx, mut rx) = mpsc::channel(100);
|
|
|
|
|
+
|
|
|
|
|
+ // Spawn sender
|
|
|
|
|
+ tokio::spawn(async move {
|
|
|
|
|
+ for i in 0..10 {
|
|
|
|
|
+ tx.send(i).await.unwrap();
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Receive
|
|
|
|
|
+ while let Some(value) = rx.recv().await {
|
|
|
|
|
+ println!("Received: {}", value);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Async Traits
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+use async_trait::async_trait;
|
|
|
|
|
+
|
|
|
|
|
+#[async_trait]
|
|
|
|
|
+pub trait DataStore {
|
|
|
|
|
+ async fn get(&self, key: &str) -> Option<String>;
|
|
|
|
|
+ async fn set(&self, key: &str, value: String) -> Result<()>;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#[async_trait]
|
|
|
|
|
+impl DataStore for RedisStore {
|
|
|
|
|
+ async fn get(&self, key: &str) -> Option<String> {
|
|
|
|
|
+ self.client.get(key).await.ok()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async fn set(&self, key: &str, value: String) -> Result<()> {
|
|
|
|
|
+ self.client.set(key, value).await?;
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Part 6: Serialization with Serde
|
|
|
|
|
+
|
|
|
|
|
+### Basic Serde
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+use serde::{Deserialize, Serialize};
|
|
|
|
|
+
|
|
|
|
|
+#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
|
+pub struct User {
|
|
|
|
|
+ pub id: u64,
|
|
|
|
|
+ pub name: String,
|
|
|
|
|
+ #[serde(default)]
|
|
|
|
|
+ pub active: bool,
|
|
|
|
|
+ #[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
|
+ pub email: Option<String>,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// JSON
|
|
|
|
|
+let json = serde_json::to_string(&user)?;
|
|
|
|
|
+let user: User = serde_json::from_str(&json)?;
|
|
|
|
|
+
|
|
|
|
|
+// TOML
|
|
|
|
|
+let toml = toml::to_string(&config)?;
|
|
|
|
|
+let config: Config = toml::from_str(&toml)?;
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Serde Attributes
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+#[derive(Serialize, Deserialize)]
|
|
|
|
|
+pub struct Config {
|
|
|
|
|
+ #[serde(rename = "serverPort")]
|
|
|
|
|
+ pub server_port: u16,
|
|
|
|
|
+
|
|
|
|
|
+ #[serde(default = "default_timeout")]
|
|
|
|
|
+ pub timeout: u64,
|
|
|
|
|
+
|
|
|
|
|
+ #[serde(skip)]
|
|
|
|
|
+ pub internal: InternalState,
|
|
|
|
|
+
|
|
|
|
|
+ #[serde(flatten)]
|
|
|
|
|
+ pub extra: HashMap<String, Value>,
|
|
|
|
|
+
|
|
|
|
|
+ #[serde(with = "chrono::serde::ts_seconds")]
|
|
|
|
|
+ pub timestamp: DateTime<Utc>,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+fn default_timeout() -> u64 { 30 }
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Custom Serialization
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+use serde::{Serializer, Deserializer};
|
|
|
|
|
+
|
|
|
|
|
+#[derive(Debug)]
|
|
|
|
|
+pub struct Url(String);
|
|
|
|
|
+
|
|
|
|
|
+impl Serialize for Url {
|
|
|
|
|
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
|
|
|
+ where
|
|
|
|
|
+ S: Serializer,
|
|
|
|
|
+ {
|
|
|
|
|
+ serializer.serialize_str(&self.0)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+impl<'de> Deserialize<'de> for Url {
|
|
|
|
|
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
|
|
|
+ where
|
|
|
|
|
+ D: Deserializer<'de>,
|
|
|
|
|
+ {
|
|
|
|
|
+ let s = String::deserialize(deserializer)?;
|
|
|
|
|
+ if s.starts_with("http") {
|
|
|
|
|
+ Ok(Url(s))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ Err(serde::de::Error::custom("Invalid URL"))
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Part 7: Collections and Iterators
|
|
|
|
|
+
|
|
|
|
|
+### Common Collections
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+use std::collections::{HashMap, HashSet, VecDeque, BTreeMap};
|
|
|
|
|
+
|
|
|
|
|
+// Vec
|
|
|
|
|
+let mut vec = vec![1, 2, 3];
|
|
|
|
|
+vec.push(4);
|
|
|
|
|
+vec.extend([5, 6, 7]);
|
|
|
|
|
+
|
|
|
|
|
+// HashMap
|
|
|
|
|
+let mut map = HashMap::new();
|
|
|
|
|
+map.insert("key", "value");
|
|
|
|
|
+map.entry("key").or_insert("default");
|
|
|
|
|
+
|
|
|
|
|
+// HashSet
|
|
|
|
|
+let mut set = HashSet::new();
|
|
|
|
|
+set.insert(1);
|
|
|
|
|
+set.contains(&1);
|
|
|
|
|
+
|
|
|
|
|
+// VecDeque (double-ended queue)
|
|
|
|
|
+let mut deque = VecDeque::new();
|
|
|
|
|
+deque.push_back(1);
|
|
|
|
|
+deque.push_front(0);
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Iterator Methods
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+let numbers = vec![1, 2, 3, 4, 5];
|
|
|
|
|
+
|
|
|
|
|
+// Map and collect
|
|
|
|
|
+let doubled: Vec<_> = numbers.iter().map(|x| x * 2).collect();
|
|
|
|
|
+
|
|
|
|
|
+// Filter
|
|
|
|
|
+let evens: Vec<_> = numbers.iter().filter(|x| *x % 2 == 0).collect();
|
|
|
|
|
+
|
|
|
|
|
+// Fold/reduce
|
|
|
|
|
+let sum: i32 = numbers.iter().sum();
|
|
|
|
|
+let product: i32 = numbers.iter().product();
|
|
|
|
|
+let custom = numbers.iter().fold(0, |acc, x| acc + x);
|
|
|
|
|
+
|
|
|
|
|
+// Find
|
|
|
|
|
+let first_even = numbers.iter().find(|x| *x % 2 == 0);
|
|
|
|
|
+
|
|
|
|
|
+// Any/All
|
|
|
|
|
+let has_even = numbers.iter().any(|x| x % 2 == 0);
|
|
|
|
|
+let all_positive = numbers.iter().all(|x| *x > 0);
|
|
|
|
|
+
|
|
|
|
|
+// Chain
|
|
|
|
|
+let combined: Vec<_> = vec1.iter().chain(vec2.iter()).collect();
|
|
|
|
|
+
|
|
|
|
|
+// Flatten
|
|
|
|
|
+let nested = vec![vec![1, 2], vec![3, 4]];
|
|
|
|
|
+let flat: Vec<_> = nested.into_iter().flatten().collect();
|
|
|
|
|
+
|
|
|
|
|
+// Zip
|
|
|
|
|
+let pairs: Vec<_> = names.iter().zip(ages.iter()).collect();
|
|
|
|
|
+
|
|
|
|
|
+// Enumerate
|
|
|
|
|
+for (i, item) in items.iter().enumerate() {
|
|
|
|
|
+ println!("{}: {}", i, item);
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Custom Iterator
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+struct Counter {
|
|
|
|
|
+ count: usize,
|
|
|
|
|
+ max: usize,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+impl Iterator for Counter {
|
|
|
|
|
+ type Item = usize;
|
|
|
|
|
+
|
|
|
|
|
+ fn next(&mut self) -> Option<Self::Item> {
|
|
|
|
|
+ if self.count < self.max {
|
|
|
|
|
+ self.count += 1;
|
|
|
|
|
+ Some(self.count)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ None
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+impl Counter {
|
|
|
|
|
+ fn new(max: usize) -> Self {
|
|
|
|
|
+ Counter { count: 0, max }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Part 8: Smart Pointers
|
|
|
|
|
+
|
|
|
|
|
+### Box, Rc, Arc
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+// Box - heap allocation, single owner
|
|
|
|
|
+let boxed = Box::new(5);
|
|
|
|
|
+let large_data = Box::new([0u8; 1_000_000]);
|
|
|
|
|
+
|
|
|
|
|
+// Rc - reference counting (single-threaded)
|
|
|
|
|
+use std::rc::Rc;
|
|
|
|
|
+let data = Rc::new(vec![1, 2, 3]);
|
|
|
|
|
+let clone1 = Rc::clone(&data);
|
|
|
|
|
+let clone2 = Rc::clone(&data);
|
|
|
|
|
+
|
|
|
|
|
+// Arc - atomic reference counting (thread-safe)
|
|
|
|
|
+use std::sync::Arc;
|
|
|
|
|
+let data = Arc::new(vec![1, 2, 3]);
|
|
|
|
|
+let clone = Arc::clone(&data);
|
|
|
|
|
+std::thread::spawn(move || {
|
|
|
|
|
+ println!("{:?}", clone);
|
|
|
|
|
+});
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### RefCell and Mutex
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+// RefCell - interior mutability (single-threaded)
|
|
|
|
|
+use std::cell::RefCell;
|
|
|
|
|
+
|
|
|
|
|
+let data = RefCell::new(5);
|
|
|
|
|
+*data.borrow_mut() += 1;
|
|
|
|
|
+
|
|
|
|
|
+// Mutex - interior mutability (thread-safe)
|
|
|
|
|
+use std::sync::Mutex;
|
|
|
|
|
+
|
|
|
|
|
+let data = Arc::new(Mutex::new(vec![]));
|
|
|
|
|
+let clone = Arc::clone(&data);
|
|
|
|
|
+
|
|
|
|
|
+std::thread::spawn(move || {
|
|
|
|
|
+ let mut lock = clone.lock().unwrap();
|
|
|
|
|
+ lock.push(1);
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// RwLock - multiple readers OR single writer
|
|
|
|
|
+use std::sync::RwLock;
|
|
|
|
|
+
|
|
|
|
|
+let data = RwLock::new(vec![1, 2, 3]);
|
|
|
|
|
+let read = data.read().unwrap(); // Multiple readers OK
|
|
|
|
|
+let mut write = data.write().unwrap(); // Exclusive write
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Cow (Clone on Write)
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+use std::borrow::Cow;
|
|
|
|
|
+
|
|
|
|
|
+fn process(input: &str) -> Cow<str> {
|
|
|
|
|
+ if input.contains(' ') {
|
|
|
|
|
+ Cow::Owned(input.replace(' ', "_"))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ Cow::Borrowed(input)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Avoids allocation when not needed
|
|
|
|
|
+let result = process("hello"); // Borrowed, no allocation
|
|
|
|
|
+let result = process("hello world"); // Owned, allocates
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Part 9: Testing
|
|
|
|
|
+
|
|
|
|
|
+### Unit Tests
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+#[cfg(test)]
|
|
|
|
|
+mod tests {
|
|
|
|
|
+ use super::*;
|
|
|
|
|
+
|
|
|
|
|
+ #[test]
|
|
|
|
|
+ fn test_add() {
|
|
|
|
|
+ assert_eq!(add(2, 3), 5);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #[test]
|
|
|
|
|
+ #[should_panic(expected = "division by zero")]
|
|
|
|
|
+ fn test_divide_by_zero() {
|
|
|
|
|
+ divide(1, 0);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #[test]
|
|
|
|
|
+ fn test_result() -> Result<(), String> {
|
|
|
|
|
+ let result = parse("42")?;
|
|
|
|
|
+ assert_eq!(result, 42);
|
|
|
|
|
+ Ok(())
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Async Tests
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+#[tokio::test]
|
|
|
|
|
+async fn test_async_function() {
|
|
|
|
|
+ let result = fetch_data().await;
|
|
|
|
|
+ assert!(result.is_ok());
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
|
|
|
+async fn test_concurrent() {
|
|
|
|
|
+ // Uses multi-threaded runtime
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Integration Tests
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+// tests/integration_test.rs
|
|
|
|
|
+use mylib::public_function;
|
|
|
|
|
+
|
|
|
|
|
+#[test]
|
|
|
|
|
+fn test_public_api() {
|
|
|
|
|
+ let result = public_function();
|
|
|
|
|
+ assert!(result.is_ok());
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Doc Tests
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+/// Adds two numbers.
|
|
|
|
|
+///
|
|
|
|
|
+/// # Examples
|
|
|
|
|
+///
|
|
|
|
|
+/// ```
|
|
|
|
|
+/// use mylib::add;
|
|
|
|
|
+/// assert_eq!(add(2, 3), 5);
|
|
|
|
|
+/// ```
|
|
|
|
|
+pub fn add(a: i32, b: i32) -> i32 {
|
|
|
|
|
+ a + b
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Part 10: Project Structure
|
|
|
|
|
+
|
|
|
|
|
+### Cargo.toml
|
|
|
|
|
+
|
|
|
|
|
+```toml
|
|
|
|
|
+[package]
|
|
|
|
|
+name = "myapp"
|
|
|
|
|
+version = "0.1.0"
|
|
|
|
|
+edition = "2021"
|
|
|
|
|
+authors = ["Your Name <you@example.com>"]
|
|
|
|
|
+description = "My Application"
|
|
|
|
|
+license = "MIT"
|
|
|
|
|
+
|
|
|
|
|
+[dependencies]
|
|
|
|
|
+tokio = { version = "1", features = ["full"] }
|
|
|
|
|
+serde = { version = "1", features = ["derive"] }
|
|
|
|
|
+serde_json = "1"
|
|
|
|
|
+thiserror = "1"
|
|
|
|
|
+anyhow = "1"
|
|
|
|
|
+tracing = "0.1"
|
|
|
|
|
+tracing-subscriber = "0.3"
|
|
|
|
|
+
|
|
|
|
|
+[dev-dependencies]
|
|
|
|
|
+mockall = "0.11"
|
|
|
|
|
+tempfile = "3"
|
|
|
|
|
+
|
|
|
|
|
+[features]
|
|
|
|
|
+default = []
|
|
|
|
|
+full = ["feature-a", "feature-b"]
|
|
|
|
|
+feature-a = []
|
|
|
|
|
+feature-b = ["dep:optional-dep"]
|
|
|
|
|
+
|
|
|
|
|
+[profile.release]
|
|
|
|
|
+lto = true
|
|
|
|
|
+codegen-units = 1
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### Module Structure
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+src/
|
|
|
|
|
+├── main.rs
|
|
|
|
|
+├── lib.rs
|
|
|
|
|
+├── config.rs
|
|
|
|
|
+├── error.rs
|
|
|
|
|
+├── models/
|
|
|
|
|
+│ ├── mod.rs
|
|
|
|
|
+│ └── user.rs
|
|
|
|
|
+├── services/
|
|
|
|
|
+│ ├── mod.rs
|
|
|
|
|
+│ └── user_service.rs
|
|
|
|
|
+└── handlers/
|
|
|
|
|
+ ├── mod.rs
|
|
|
|
|
+ └── user_handler.rs
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+```rust
|
|
|
|
|
+// src/lib.rs
|
|
|
|
|
+pub mod config;
|
|
|
|
|
+pub mod error;
|
|
|
|
|
+pub mod models;
|
|
|
|
|
+pub mod services;
|
|
|
|
|
+pub mod handlers;
|
|
|
|
|
+
|
|
|
|
|
+pub use error::Error;
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Quality Checklist
|
|
|
|
|
+
|
|
|
|
|
+- [ ] No unnecessary clones (use references)
|
|
|
|
|
+- [ ] Proper error handling (Result, ?)
|
|
|
|
|
+- [ ] Thread safety verified (Arc, Mutex where needed)
|
|
|
|
|
+- [ ] Lifetimes explicit where required
|
|
|
|
|
+- [ ] Tests cover edge cases
|
|
|
|
|
+- [ ] clippy warnings resolved
|
|
|
|
|
+- [ ] cargo fmt applied
|
|
|
|
|
+- [ ] Documentation for public API
|
|
|
|
|
+- [ ] No unwrap() in library code
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## Canonical Resources
|
|
|
|
|
+
|
|
|
|
|
+- [The Rust Book](https://doc.rust-lang.org/book/)
|
|
|
|
|
+- [Rust by Example](https://doc.rust-lang.org/rust-by-example/)
|
|
|
|
|
+- [Rustlings](https://github.com/rust-lang/rustlings)
|
|
|
|
|
+- [docs.rs](https://docs.rs/)
|
|
|
|
|
+- [Tokio Tutorial](https://tokio.rs/tokio/tutorial)
|
|
|
|
|
+- [Serde Documentation](https://serde.rs/)
|