qubit_common/lang/box_error.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2025 - 2026.
4 * Haixing Hu, Qubit Co. Ltd.
5 *
6 * All rights reserved.
7 *
8 ******************************************************************************/
9//! # Boxed Error Type Alias
10//!
11//! Provides type aliases for boxed dynamic error types to simplify error
12//! handling in generic contexts.
13//!
14//! # Author
15//!
16//! Haixing Hu
17
18use std::error::Error;
19
20/// A type alias for `Box<dyn Error + Send + Sync>`.
21///
22/// This type represents a boxed, dynamically-dispatched error that can be
23/// safely sent across thread boundaries and shared between threads.
24///
25/// # Why Use This Type Alias?
26///
27/// In Rust, `dyn Error` is a dynamically sized type (DST), which means it
28/// cannot be stored directly on the stack or in a struct field. To work with
29/// trait objects like `dyn Error`, we must place them behind a pointer, such
30/// as `Box`, `Arc`, or `Rc`.
31///
32/// The type `Box<dyn Error + Send + Sync>` is commonly used throughout the
33/// codebase for the following reasons:
34///
35/// 1. **Type Erasure**: Allows functions to return or store errors of
36/// different concrete types without exposing the specific error type.
37///
38/// 2. **Thread Safety**: The `Send + Sync` bounds ensure the error can be
39/// transferred across thread boundaries and shared safely in concurrent
40/// contexts.
41///
42/// 3. **Flexibility**: Enables generic error handling where the exact error
43/// type is not known at compile time.
44///
45/// However, repeatedly writing `Box<dyn Error + Send + Sync>` leads to:
46///
47/// - **Code Verbosity**: Long type signatures that reduce readability.
48/// - **Inconsistency Risk**: Easy to accidentally omit `Send` or `Sync`
49/// bounds, leading to compilation errors or thread-safety issues.
50/// - **Maintenance Burden**: If we decide to change the error representation
51/// (e.g., to `Arc<dyn Error + Send + Sync>`), we would need to update
52/// every occurrence.
53///
54/// By defining a type alias `BoxError`, we:
55///
56/// - **Reduce Complexity**: Simplify function signatures, struct fields, and
57/// generic constraints.
58/// - **Ensure Consistency**: Guarantee that all boxed errors have the same
59/// bounds (`Send + Sync`).
60/// - **Improve Maintainability**: Centralize the definition, making future
61/// changes easier.
62///
63/// # Comparison with Standard Library
64///
65/// The Rust standard library uses concrete error types rather than type
66/// aliases for trait objects:
67///
68/// - `std::io::Error`: A concrete type that internally uses `Box` to store
69/// error details.
70/// - `std::fmt::Error`: A zero-sized type (ZST) representing formatting
71/// errors.
72///
73/// These are not trait objects, so they don't require boxing. In contrast,
74/// `BoxError` is a type alias for a trait object (`dyn Error`), which must
75/// be boxed because it is a DST.
76///
77/// # When to Use `BoxError`
78///
79/// Use `BoxError` in the following scenarios:
80///
81/// 1. **Generic Error Handling**: When a function needs to return errors of
82/// different types without exposing the specific error type.
83///
84/// 2. **Closure Signatures**: To simplify closure signatures that return
85/// errors, especially in higher-order functions.
86///
87/// 3. **Struct Fields**: When storing errors in struct fields where the
88/// exact error type is not known at compile time.
89///
90/// 4. **Trait Implementations**: When implementing traits that require
91/// returning a generic error type.
92///
93/// # When NOT to Use `BoxError`
94///
95/// Avoid using `BoxError` in the following cases:
96///
97/// 1. **Public APIs with Specific Errors**: If your public API should expose
98/// specific error types (e.g., using `thiserror` or custom error enums),
99/// do not use `BoxError`. Concrete error types provide better error
100/// handling and debugging experience.
101///
102/// 2. **Performance-Critical Code**: Boxing introduces a heap allocation. If
103/// performance is critical and the error type is known, use a concrete
104/// error type instead.
105///
106/// 3. **Error Recovery**: If the caller needs to match on specific error
107/// variants to recover from errors, use a concrete error enum instead of
108/// `BoxError`.
109///
110/// # Examples
111///
112/// ## Example 1: Simplifying Function Signatures
113///
114/// ```rust
115/// use qubit_common::BoxError;
116///
117/// // Without BoxError (verbose and error-prone)
118/// fn operation_verbose() -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
119/// Ok("success".to_string())
120/// }
121///
122/// // With BoxError (concise and consistent)
123/// fn operation_concise() -> Result<String, BoxError> {
124/// Ok("success".to_string())
125/// }
126/// ```
127///
128/// ## Example 2: Simplifying Struct Fields
129///
130/// ```rust
131/// use qubit_common::BoxError;
132///
133/// // Without BoxError
134/// struct TaskResult {
135/// success: bool,
136/// error: Option<Box<dyn std::error::Error + Send + Sync>>,
137/// }
138///
139/// // With BoxError (more readable)
140/// struct TaskResultSimple {
141/// success: bool,
142/// error: Option<BoxError>,
143/// }
144/// ```
145///
146/// ## Example 3: Simplifying Closure Signatures
147///
148/// ```rust
149/// use qubit_common::BoxError;
150///
151/// // Without BoxError (complex generic constraint)
152/// fn execute_with_callback<F>(callback: F)
153/// where
154/// F: FnOnce() -> Result<(), Box<dyn std::error::Error + Send + Sync>>,
155/// {
156/// let _ = callback();
157/// }
158///
159/// // With BoxError (cleaner constraint)
160/// fn execute_with_callback_simple<F>(callback: F)
161/// where
162/// F: FnOnce() -> Result<(), BoxError>,
163/// {
164/// let _ = callback();
165/// }
166/// ```
167///
168/// ## Example 4: Converting Concrete Errors to BoxError
169///
170/// ```rust
171/// use qubit_common::BoxError;
172/// use std::io;
173///
174/// fn read_file() -> Result<String, BoxError> {
175/// let content = std::fs::read_to_string("file.txt")
176/// .map_err(|e| Box::new(e) as BoxError)?;
177/// Ok(content)
178/// }
179/// ```
180///
181/// # Author
182///
183/// Haixing Hu
184pub type BoxError = Box<dyn Error + Send + Sync>;
185
186/// A type alias for `Result<T, BoxError>`.
187///
188/// This is a convenient shorthand for `Result<T, Box<dyn Error + Send +
189/// Sync>>`, commonly used in functions that return a value or a boxed error.
190///
191/// # Why Use This Type Alias?
192///
193/// Using `BoxResult<T>` instead of `Result<T, BoxError>` further simplifies
194/// function signatures and makes the code more readable. It is particularly
195/// useful in codebases where most functions return `Result` with `BoxError`
196/// as the error type.
197///
198/// # When to Use `BoxResult<T>`
199///
200/// Use `BoxResult<T>` when:
201///
202/// 1. **Consistent Error Type**: Your function returns a `Result` with
203/// `BoxError` as the error type.
204///
205/// 2. **Simplified Signatures**: You want to reduce the verbosity of
206/// function signatures.
207///
208/// 3. **Codebase Consistency**: You want to maintain a consistent style
209/// across your codebase.
210///
211/// # When NOT to Use `BoxResult<T>`
212///
213/// Avoid using `BoxResult<T>` when:
214///
215/// 1. **Specific Error Types**: Your function should return a specific error
216/// type for better error handling.
217///
218/// 2. **Standard Library Compatibility**: You are implementing traits from
219/// the standard library that expect `Result<T, E>` with a specific `E`.
220///
221/// # Examples
222///
223/// ## Example 1: Simplifying Function Return Types
224///
225/// ```rust
226/// use qubit_common::BoxResult;
227///
228/// // Without BoxResult
229/// fn parse_config() -> Result<Config, Box<dyn std::error::Error + Send + Sync>> {
230/// // Implementation
231/// # Ok(Config {})
232/// }
233///
234/// // With BoxResult (more concise)
235/// fn parse_config_simple() -> BoxResult<Config> {
236/// // Implementation
237/// # Ok(Config {})
238/// }
239///
240/// # struct Config {}
241/// ```
242///
243/// ## Example 2: Using with the `?` Operator
244///
245/// ```rust
246/// use qubit_common::BoxResult;
247/// use std::fs;
248///
249/// fn load_and_parse() -> BoxResult<String> {
250/// let content = fs::read_to_string("config.toml")
251/// .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)?;
252/// Ok(content.to_uppercase())
253/// }
254/// ```
255///
256/// ## Example 3: Combining Multiple Error Types
257///
258/// ```rust
259/// use qubit_common::BoxResult;
260/// use std::{fs, io};
261///
262/// fn process_file(path: &str) -> BoxResult<usize> {
263/// let content = fs::read_to_string(path)
264/// .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)?;
265///
266/// let parsed: usize = content.trim().parse()
267/// .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)?;
268///
269/// Ok(parsed * 2)
270/// }
271/// ```
272///
273/// # Author
274///
275/// Haixing Hu
276pub type BoxResult<T> = Result<T, BoxError>;