Clean Code на Rust: принципы и примеры
Writing clean code is not just about following trendy fads, but a fundamental skill that distinguishes a professional from an amateur. In the world of systems programming, where Rust is gaining more and more popularity, the ability to write understandable, maintainable, and safe code becomes critically important. In this article, we will break down the main principles of Clean Code as applied to Rust, backing them up with practical examples.
1. Naming: say what the variable does
Proper naming is the foundation of readable code. In Rust, where strong typing and ownership already provide a lot of information, good names make the code self-documenting.
Bad example:
fn calc(x: &[i32]) -> i32 { let mut r = 0; for i in x { r += i; } r}Good example (Clean Code):
fn calculate_sum(numbers: &[i32]) -> i32 { let mut total = 0; for number in numbers { total += number; } total}Use verbs for functions (calculate, fetch, validate) and nouns for variables (total, user_name, config). Avoid abbreviations like tmp, res, or val — they say nothing about the purpose.
2. Functions: small and with a single responsibility
The Single Responsibility principle applies not only to classes but also to functions. Each function should do exactly one thing and do it well.
Bad example (function does too much):
fn process_user_data(user: &User) -> Result<(), String> { // Validation if user.name.is_empty() { return Err("Name is empty".to_string()); } // Logging println!("Processing user: {}", user.name); // Saving to DB save_to_database(user)?; // Sending email send_welcome_email(user.email)?; Ok(())}Good example (split into small functions):
fn process_user_data(user: &User) -> Result<(), AppError> { validate_user(user)?; log_user_processing(user); save_user_to_db(user)?; send_welcome_email(&user.email)?; Ok(())}
fn validate_user(user: &User) -> Result<(), AppError> { if user.name.is_empty() { return Err(AppError::Validation("Name is required".into())); } Ok(())}
fn log_user_processing(user: &User) { println!("Processing user: {}", user.name);}Notice: each function now has a clear responsibility, and the code has become easier to test and modify.
3. Error handling: use Result and your own error types
Rust has no exceptions, and that's great. Clean Code in Rust means explicit error handling through Result<T, E> and Option<T> types. Avoid panics (unwrap(), expect()) in production code.
Bad example (panic on error):
fn read_config(path: &str) -> Config { let content = std::fs::read_to_string(path).unwrap(); serde_json::from_str(&content).unwrap()}Good example (clean error handling):
#[derive(Debug, thiserror::Error)]pub enum ConfigError { #[error("Failed to read file: {0}")] IoError(#[from] std::io::Error), #[error("Failed to parse config: {0}")] ParseError(#[from] serde_json::Error),}
fn read_config(path: &str) -> Result<Config, ConfigError> { let content = std::fs::read_to_string(path)?; let config: Config = serde_json::fr