Skip to main content

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>;