exn/lib.rs
1// Copyright 2025 FastLabs Developers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! A context-aware concrete Error type built on `std::error::Error`
16//!
17//! # Examples
18//!
19//! ```
20//! use exn::Result;
21//! use exn::ResultExt;
22//! use exn::bail;
23//!
24//! // It's recommended to define errors as structs. Exn will maintain the error tree automatically.
25//! #[derive(Debug)]
26//! struct LogicError(String);
27//!
28//! impl std::fmt::Display for LogicError {
29//! fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30//! write!(f, "logic error: {}", self.0)
31//! }
32//! }
33//!
34//! impl std::error::Error for LogicError {}
35//!
36//! fn do_logic() -> Result<(), LogicError> {
37//! bail!(LogicError("0 == 1".to_string()));
38//! }
39//!
40//! // Errors can be enum but notably don't need to chain source error.
41//! #[derive(Debug)]
42//! enum AppError {
43//! Fatal { consequences: &'static str },
44//! Trivial,
45//! }
46//!
47//! impl std::fmt::Display for AppError {
48//! fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49//! match self {
50//! AppError::Fatal { consequences } => write!(f, "fatal error: {consequences}"),
51//! AppError::Trivial => write!(f, "trivial error"),
52//! }
53//! }
54//! }
55//!
56//! impl std::error::Error for AppError {}
57//!
58//! fn main() {
59//! if let Err(err) = do_logic().or_raise(|| AppError::Fatal {
60//! consequences: "math no longer works",
61//! }) {
62//! eprintln!("{err:?}");
63//! }
64//! }
65//! ```
66//!
67//! The above program will print an error message like:
68//!
69//! ```text
70//! fatal error: math no longer works, at exn/src/lib.rs:44:16
71//! |
72//! |-> logic error: 0 == 1, at exn/src/lib.rs:40:5
73//! ```
74
75#![cfg_attr(docsrs, feature(doc_cfg))]
76#![deny(missing_docs)]
77
78mod debug;
79mod display;
80mod impls;
81mod macros;
82mod option;
83mod result;
84
85pub use self::impls::Exn;
86pub use self::impls::Frame;
87pub use self::option::OptionExt;
88pub use self::result::Result;
89pub use self::result::ResultExt;
90
91/// A trait bound of the supported error type of [`Exn`].
92pub trait Error: std::error::Error + std::any::Any + Send + Sync + 'static {
93 /// Raise this error as a new exception.
94 #[track_caller]
95 fn raise(self) -> Exn<Self>
96 where
97 Self: Sized,
98 {
99 Exn::new(self)
100 }
101}
102
103impl<T> Error for T where T: std::error::Error + std::any::Any + Send + Sync + 'static {}
104
105/// Equivalent to `Ok::<_, Exn<E>>(value)`.
106///
107/// This simplifies creation of an `exn::Result` in places where type inference cannot deduce the
108/// `E` type of the result — without needing to write `Ok::<_, Exn<E>>(value)`.
109///
110/// One might think that `exn::Result::Ok(value)` would work in such cases, but it does not.
111///
112/// ```console
113/// error[E0282]: type annotations needed for `std::result::Result<i32, E>`
114/// --> src/main.rs:11:13
115/// |
116/// 11 | let _ = exn::Result::Ok(1);
117/// | - ^^^^^^^^^^^^^^^ cannot infer type for type parameter `E` declared on the enum `Result`
118/// | |
119/// | consider giving this pattern the explicit type `std::result::Result<i32, E>`, where the type parameter `E` is specified
120/// ```
121#[expect(non_snake_case)]
122pub fn Ok<T, E: Error>(value: T) -> Result<T, E> {
123 Result::Ok(value)
124}