Skip to main content
Glama
rust-development.mdc14.3 kB
--- description: "Rust development: ownership, lifetimes, error handling, async patterns, and cargo workflows" globs: ["**/*.rs", "**/Cargo.toml", "**/Cargo.lock"] alwaysApply: false --- # Rust Development Patterns Idiomatic Rust patterns focusing on ownership, safety, and performance. ## CRITICAL: Agentic-First Rust Development ### Pre-Development Verification (MANDATORY) Before writing ANY Rust code: ``` 1. CHECK RUST INSTALLATION → run_terminal_cmd("rustc --version") → run_terminal_cmd("cargo --version") 2. VERIFY CURRENT VERSIONS (use web_search) → web_search("Rust stable version December 2024") → web_search("Rust edition 2024 features") 3. CHECK EXISTING PROJECT → Does Cargo.toml exist? Read it first! → What Rust edition is specified? → What dependencies are already present? 4. FOR NEW PROJECTS - USE cargo new → NEVER manually create Cargo.toml from scratch → run_terminal_cmd("cargo new my_project") → run_terminal_cmd("cargo new --lib my_library") ``` ### CLI-First Rust Development **ALWAYS use Cargo CLI:** ```bash # Project creation (NEVER manually create Cargo.toml) cargo new my_project cargo new --lib my_library cargo init # In existing directory # Add dependencies (NEVER manually edit Cargo.toml for deps) cargo add tokio --features full cargo add serde --features derive cargo add thiserror cargo add anyhow # Development dependencies cargo add --dev tokio-test cargo add --dev mockall # Build and verify cargo build cargo check # Faster than build, just checks cargo clippy # Linting cargo fmt # Format code # Test cargo test cargo test -- --nocapture # See println output ``` ### Post-Edit Verification After ANY Rust code changes, ALWAYS run: ```bash # Check compilation cargo check # Lint for issues cargo clippy -- -D warnings # Run tests cargo test # Format code cargo fmt --check ``` ### Common Rust Syntax Traps (Avoid These!) ```rust // WRONG: Using unwrap in production code let value = some_option.unwrap(); // Panics on None! let data = result.unwrap(); // Panics on Err! // CORRECT: Handle errors properly let value = some_option.ok_or(MyError::NotFound)?; let data = result.map_err(|e| MyError::from(e))?; // WRONG: Borrowing across await points async fn bad_example(data: &mut Data) { let reference = &data.field; async_operation().await; // reference held across await! use_reference(reference); } // CORRECT: Clone or restructure async fn good_example(data: &mut Data) { let value = data.field.clone(); async_operation().await; use_value(value); } // WRONG: Missing Send bound for async traits trait MyAsyncTrait { async fn do_work(&self); // Won't compile in multi-threaded! } // CORRECT: Add Send bound when needed trait MyAsyncTrait: Send + Sync { fn do_work(&self) -> impl Future<Output = ()> + Send; } // WRONG: String vs &str confusion fn greet(name: String) { } // Takes ownership unnecessarily greet("hello".to_string()); // Wasteful allocation // CORRECT: Accept borrowed when possible fn greet(name: &str) { } // Borrows, no allocation needed greet("hello"); // Works directly with string literal ``` ### Cargo.toml Best Practices ```toml [package] name = "myproject" version = "0.1.0" edition = "2021" # Always specify edition rust-version = "1.75" # Minimum Rust version [dependencies] # Pin to semver-compatible range tokio = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] } [dev-dependencies] tokio-test = "0.4" # Optimize release builds [profile.release] lto = true codegen-units = 1 ``` --- ## Ownership & Borrowing ### Core Principles 1. Each value has exactly one owner 2. When the owner goes out of scope, the value is dropped 3. References must always be valid 4. Either one mutable reference OR any number of immutable references ### Borrowing Patterns ```rust // Immutable borrow - read only fn print_length(s: &str) { println!("Length: {}", s.len()); } // Mutable borrow - can modify fn append_suffix(s: &mut String) { s.push_str("_suffix"); } // Taking ownership - consumes the value fn consume_string(s: String) { println!("Consumed: {}", s); // s is dropped here } // Returning ownership fn create_string() -> String { String::from("created") } ``` ### When to Use What - `&T`: Reading data, most function parameters - `&mut T`: Modifying data in place - `T`: Taking ownership, returning from functions, storing in structs --- ## Lifetimes ### Basic Lifetime Annotations ```rust // The returned reference lives as long as the shortest input lifetime fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } // Struct with references struct ImportantExcerpt<'a> { part: &'a str, } impl<'a> ImportantExcerpt<'a> { fn level(&self) -> i32 { 3 } fn announce_and_return_part(&self, announcement: &str) -> &str { println!("Attention: {}", announcement); self.part } } ``` ### Lifetime Elision Rules The compiler infers lifetimes when: 1. Each reference parameter gets its own lifetime 2. If exactly one input lifetime, it's assigned to all outputs 3. If `&self` or `&mut self`, that lifetime is assigned to outputs ```rust // These are equivalent: fn first_word(s: &str) -> &str { ... } fn first_word<'a>(s: &'a str) -> &'a str { ... } ``` --- ## Error Handling ### Result and Option ```rust use std::fs::File; use std::io::{self, Read}; // Using Result fn read_file(path: &str) -> Result<String, io::Error> { let mut file = File::open(path)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; Ok(contents) } // Using Option fn find_user(id: u64) -> Option<User> { users.iter().find(|u| u.id == id).cloned() } // Combining with ? fn get_user_email(id: u64) -> Option<String> { let user = find_user(id)?; Some(user.email) } ``` ### Custom Error Types ```rust use thiserror::Error; #[derive(Error, Debug)] pub enum AppError { #[error("Database error: {0}")] Database(#[from] sqlx::Error), #[error("Not found: {resource} with id {id}")] NotFound { resource: String, id: u64 }, #[error("Validation error: {0}")] Validation(String), } // Using anyhow for applications use anyhow::{Context, Result}; fn load_config() -> Result<Config> { let contents = std::fs::read_to_string("config.toml") .context("Failed to read config file")?; let config: Config = toml::from_str(&contents) .context("Failed to parse config")?; Ok(config) } ``` ### Error Handling Best Practices - Use `thiserror` for library error types - Use `anyhow` for application error handling - Prefer `?` operator over `unwrap()` in production code - Use `expect()` only when panic is truly unrecoverable --- ## Async Rust ### Async/Await Basics ```rust use tokio; #[tokio::main] async fn main() { let result = fetch_data("https://api.example.com").await; println!("{:?}", result); } async fn fetch_data(url: &str) -> Result<String, reqwest::Error> { let response = reqwest::get(url).await?; let body = response.text().await?; Ok(body) } ``` ### Concurrent Operations ```rust use tokio::join; use futures::future::join_all; // Run multiple futures concurrently async fn fetch_all() -> (Data1, Data2) { let (data1, data2) = join!( fetch_data1(), fetch_data2() ); (data1.unwrap(), data2.unwrap()) } // Dynamic number of futures async fn fetch_many(urls: Vec<String>) -> Vec<String> { let futures: Vec<_> = urls.iter() .map(|url| fetch_data(url)) .collect(); join_all(futures).await .into_iter() .filter_map(|r| r.ok()) .collect() } ``` ### Streams ```rust use tokio_stream::StreamExt; async fn process_stream() { let mut stream = tokio_stream::iter(vec![1, 2, 3]); while let Some(value) = stream.next().await { println!("{}", value); } } ``` --- ## Common Patterns ### Builder Pattern ```rust #[derive(Default)] pub struct RequestBuilder { url: String, method: Method, headers: HashMap<String, String>, body: Option<Vec<u8>>, } impl RequestBuilder { pub fn new(url: impl Into<String>) -> Self { Self { url: url.into(), ..Default::default() } } pub fn method(mut self, method: Method) -> Self { self.method = method; self } pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self { self.headers.insert(key.into(), value.into()); self } pub fn body(mut self, body: Vec<u8>) -> Self { self.body = Some(body); self } pub fn build(self) -> Request { Request { url: self.url, method: self.method, headers: self.headers, body: self.body, } } } // Usage let request = RequestBuilder::new("https://api.example.com") .method(Method::POST) .header("Content-Type", "application/json") .body(data) .build(); ``` ### Newtype Pattern ```rust // Type-safe wrappers pub struct UserId(pub u64); pub struct OrderId(pub u64); // Can't accidentally pass OrderId where UserId expected fn get_user(id: UserId) -> Option<User> { ... } // Implement traits as needed impl From<u64> for UserId { fn from(id: u64) -> Self { UserId(id) } } ``` ### Type State Pattern ```rust pub struct Request<State> { inner: RequestInner, _state: PhantomData<State>, } pub struct Draft; pub struct Ready; impl Request<Draft> { pub fn new() -> Self { ... } pub fn set_url(mut self, url: String) -> Self { self.inner.url = Some(url); self } pub fn finalize(self) -> Result<Request<Ready>, ValidationError> { // Validate and transition state Ok(Request { inner: self.inner, _state: PhantomData }) } } impl Request<Ready> { pub async fn send(self) -> Result<Response, Error> { // Only Ready requests can be sent } } ``` --- ## Traits & Generics ### Trait Definitions ```rust pub trait Repository<T> { fn get(&self, id: u64) -> Option<T>; fn save(&mut self, item: T) -> Result<(), Error>; fn delete(&mut self, id: u64) -> Result<(), Error>; } // Default implementations pub trait Greet { fn name(&self) -> &str; fn greet(&self) -> String { format!("Hello, {}!", self.name()) } } ``` ### Trait Bounds ```rust // Single bound fn print_debug<T: Debug>(item: T) { println!("{:?}", item); } // Multiple bounds fn process<T: Clone + Send + Sync>(item: T) { ... } // Where clauses for complex bounds fn complex_function<T, U>(t: T, u: U) -> Result<(), Error> where T: Repository<User> + Clone, U: Into<String> + Send, { // ... } // Impl trait for return types fn create_iterator() -> impl Iterator<Item = i32> { (0..10).filter(|x| x % 2 == 0) } ``` --- ## Cargo & Project Structure ### Cargo.toml ```toml [package] name = "myproject" version = "0.1.0" edition = "2021" rust-version = "1.75" [dependencies] tokio = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] } thiserror = "1" anyhow = "1" [dev-dependencies] tokio-test = "0.4" [features] default = [] full = ["feature-a", "feature-b"] feature-a = [] feature-b = ["dep:optional-dep"] [profile.release] lto = true codegen-units = 1 ``` ### Project Layout ``` myproject/ src/ lib.rs # Library root main.rs # Binary entry point config.rs # Configuration error.rs # Error types models/ # Data structures mod.rs user.rs services/ # Business logic mod.rs handlers/ # Request handlers mod.rs tests/ # Integration tests integration_test.rs benches/ # Benchmarks benchmark.rs examples/ # Example binaries example.rs ``` ### Module Organization ```rust // src/lib.rs pub mod config; pub mod error; pub mod models; pub mod services; pub use error::Error; pub use config::Config; // src/models/mod.rs mod user; mod order; pub use user::User; pub use order::Order; ``` --- ## Testing ### Unit Tests ```rust #[cfg(test)] mod tests { use super::*; #[test] fn test_add() { assert_eq!(add(2, 2), 4); } #[test] #[should_panic(expected = "division by zero")] fn test_divide_by_zero() { divide(1, 0); } #[test] fn test_result() -> Result<(), Error> { let result = fallible_function()?; assert_eq!(result, expected); Ok(()) } } ``` ### Async Tests ```rust #[tokio::test] async fn test_async_function() { let result = async_function().await; assert!(result.is_ok()); } ``` ### Integration Tests ```rust // tests/integration_test.rs use myproject::Config; #[test] fn test_config_loading() { let config = Config::load("test_config.toml").unwrap(); assert_eq!(config.port, 8080); } ``` --- ## Performance Tips ### Avoid Unnecessary Allocations ```rust // Bad: creates new String each iteration for item in items { let key = format!("key_{}", item.id); } // Good: reuse buffer let mut key = String::with_capacity(20); for item in items { key.clear(); write!(&mut key, "key_{}", item.id).unwrap(); } ``` ### Use Iterators ```rust // Bad: intermediate collections let filtered: Vec<_> = items.iter().filter(|x| x.active).collect(); let mapped: Vec<_> = filtered.iter().map(|x| x.value).collect(); let sum: i32 = mapped.iter().sum(); // Good: lazy iterator chain let sum: i32 = items.iter() .filter(|x| x.active) .map(|x| x.value) .sum(); ``` ### Smart Pointers ```rust // Rc for shared ownership (single-threaded) use std::rc::Rc; let shared = Rc::new(data); // Arc for shared ownership (multi-threaded) use std::sync::Arc; let shared = Arc::new(data); // Box for heap allocation let boxed: Box<dyn Trait> = Box::new(implementation); // Cow for clone-on-write use std::borrow::Cow; fn process(input: Cow<str>) -> Cow<str> { if needs_modification(&input) { Cow::Owned(modify(input.into_owned())) } else { input } } ```

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/madebyaris/rakitui-ai'

If you have feedback or need assistance with the MCP directory API, please join our Discord server