Robust Error Handling with Result and Custom Types for Memory-Safe Operations
Owner: SnippetBot
Created: 2026-11-01 00:01:22
Size: 5.59 KB
Expires: Never
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#![allow(dead_code)] // Allow dead code for demo purposes
use std::io;
use thiserror::Error; // Add `thiserror = "1.0"` and `rand = "0.8"` to Cargo.toml
use rand::{self, Rng};
#[derive(Debug, Error)]
enum UserServiceError {
#[error("User with ID '{0}' not found.")]
NotFound(u32),
#[error("Database error: {0}")]
Database(#[from] io::Error), // Example, uses standard library io::Error for simplicity
#[error("Validation error for user data: {0}")]
Validation(String),
#[error("Internal server error: {0}")]
Internal(String),
}
// A simple user struct for demonstration
#[derive(Debug, Clone)]
struct User {
id: u32,
name: String,
email: String,
is_active: bool,
}
struct UserService {
// In a real application, this would be a database connection pool or similar,
// ensuring thread-safe access (e.g., using Arc<Mutex<Vec<User>>>).
users: Vec<User>,
}
impl UserService {
fn new() -> Self {
UserService {
users: vec![
User { id: 1, name: "Alice".to_string(), email: "alice@example.com".to_string(), is_active: true },
User { id: 2, name: "Bob".to_string(), email: "bob@example.com".to_string(), is_active: false },
],
}
}
/// Attempts to find a user by ID. Returns an error if not found.
/// `Result` ensures callers explicitly handle both success and failure,
/// preventing accidental use of non-existent data.
fn get_user_by_id(&self, id: u32) -> Result<&User, UserServiceError> {
self.users.iter()
.find(|u| u.id == id)
.ok_or_else(|| UserServiceError::NotFound(id))
}
/// Attempts to update a user's email. Enforces validation and simulates database constraints.
/// This ensures that the application state (users data) remains valid and consistent.
fn update_user_email(&mut self, user_id: u32, new_email: &str) -> Result<(), UserServiceError> {
// 1. Input validation - prevents creating an invalid user state.
if !new_email.contains('@') || !new_email.contains('.') {
return Err(UserServiceError::Validation(format!("Invalid email format: {}", new_email)));
}
// 2. Find user (safe lookup) - `?` propagates `NotFound` error if user isn't there.
let user_index = self.users.iter().position(|u| u.id == user_id)
.ok_or_else(|| UserServiceError::NotFound(user_id))?;
// Simulate a database operation that might fail.
let mut rng = rand::thread_rng();
if rng.gen_bool(0.8) { // 80% chance of successful DB update
println!("Simulating successful database update for user {} with new email {}.", user_id, new_email);
self.users[user_index].email = new_email.to_string();
Ok(())
} else {
// If the database operation fails, the state is not updated.
Err(UserServiceError::Database(io::Error::new(io::ErrorKind::TimedOut, "DB connection timed out")))
}
}
/// Simulates a critical operation that might fail and shows graceful error propagation.
/// By using `?` and `Result`, failures in sub-operations are propagated up,
/// ensuring the overall operation's success is explicit and consistent.
fn perform_critical_operation(&mut self, user_id: u32) -> Result<(), UserServiceError> {
println!("
Attempting critical operation for user {}...", user_id);
// Attempt to update the user's email as part of a larger operation.
// If this fails, the error is immediately returned, preventing further processing
// with potentially stale or incorrect data.
self.update_user_email(user_id, &format!("updated_{}@example.com", user_id))?;
// Simulate another internal service call that might fail.
let mut rng = rand::thread_rng();
if rng.gen_bool(0.9) { // 90% chance of success
println!("Critical operation for user {} completed successfully.", user_id);
Ok(())
} else {
Err(UserServiceError::Internal(format!("Failed to notify external service for user {}", user_id)))
}
}
}
fn main() {
let mut service = UserService::new();
// Test get_user_by_id
println!("--- Testing User Lookup ---");
match service.get_user_by_id(1) {
Ok(user) => println!("Found user: {:?}", user),
Err(e) => println!("Error getting user: {}", e),
}
match service.get_user_by_id(99) {
Ok(user) => println!("Found user: {:?}", user),
Err(e) => println!("Error getting user: {}", e),
}
// Test update_user_email
println!("
--- Testing Email Updates ---");
match service.update_user_email(1, "alice.updated@example.com") {
Ok(_) => println!("Email updated for user 1."),
Err(e) => println!("Error updating email for user 1: {}", e),
}
match service.update_user_email(2, "invalid-email") {
Ok(_) => println!("Email updated for user 2."),
Err(e) => println!("Error updating email for user 2: {}", e),
}
match service.update_user_email(99, "nonexistent@example.com") {
Ok(_) => println!("Email updated for user 99."),
Err(e) => println!("Error updating email for user 99: {}", e),
}
println!("
--- Testing Critical Operation (repeatedly to show failures) ---");
// This will likely show various error types due to `rand::random()` and propagation.
for _ in 0..5 {
match service.perform_critical_operation(1) {
Ok(_) => println!("Critical operation for user 1 succeeded."),
Err(e) => println!("Critical operation for user 1 failed: {}", e),
}
}
}