From c726b8e57844ac3ba7023c8d4a5192c2d7bf2f6a Mon Sep 17 00:00:00 2001 From: tison Date: Fri, 16 Jan 2026 15:01:35 +0800 Subject: [PATCH 1/2] refactor: add Send and Sync to Error trait bound Signed-off-by: tison --- examples/src/custom-layout.rs | 8 +++--- examples/src/downcast.rs | 8 +++--- exn/src/debug.rs | 2 +- exn/src/display.rs | 2 +- exn/src/ext.rs | 53 +++++++++++++++++++++++++++++++++++ exn/src/impls.rs | 14 ++++----- exn/src/lib.rs | 24 ++-------------- exn/src/option.rs | 4 +-- exn/src/result.rs | 12 ++++---- 9 files changed, 81 insertions(+), 46 deletions(-) create mode 100644 exn/src/ext.rs diff --git a/examples/src/custom-layout.rs b/examples/src/custom-layout.rs index 348dc81..1c7fce7 100644 --- a/examples/src/custom-layout.rs +++ b/examples/src/custom-layout.rs @@ -45,11 +45,11 @@ impl std::fmt::Display for MainError { } } -impl std::error::Error for MainError {} +impl Error for MainError {} impl MainError { /// Convert an `Exn` into MainError with custom numbered list formatting. - pub fn new(err: Exn) -> Self { + pub fn new(err: Exn) -> Self { fn collect_frames(frame: &Frame, frames: &mut Vec) { // Add this frame first frames.push(format!("[{}] {}", frame.location(), frame.error())); @@ -92,7 +92,7 @@ mod app { } } - impl std::error::Error for AppError {} + impl Error for AppError {} } mod http { @@ -115,7 +115,7 @@ mod http { } } - impl std::error::Error for HttpError {} + impl Error for HttpError {} } // Output when running `cargo run --example custom_layout`: diff --git a/examples/src/downcast.rs b/examples/src/downcast.rs index 8e2f308..76ed64c 100644 --- a/examples/src/downcast.rs +++ b/examples/src/downcast.rs @@ -56,7 +56,7 @@ fn main() -> Result<(), MainError> { } /// Walk the error chain and extract HTTP status code if present. -fn extract_http_status(err: &Exn) -> Option { +fn extract_http_status(err: &Exn) -> Option { fn walk(frame: &Frame) -> Option { // Try to downcast current frame if let Some(http_err) = frame.error().downcast_ref::() { @@ -73,7 +73,7 @@ fn extract_http_status(err: &Exn) -> Option { #[derive(Debug, Display)] #[display("fatal error occurred in application")] struct MainError; -impl std::error::Error for MainError {} +impl Error for MainError {} mod app { use super::*; @@ -85,7 +85,7 @@ mod app { #[derive(Debug, Display)] pub struct AppError(String); - impl std::error::Error for AppError {} + impl Error for AppError {} } mod http { @@ -104,7 +104,7 @@ mod http { pub status: u16, pub message: String, } - impl std::error::Error for HttpError {} + impl Error for HttpError {} } // Output when running `cargo run --example downcast`: diff --git a/exn/src/debug.rs b/exn/src/debug.rs index 9234530..b266179 100644 --- a/exn/src/debug.rs +++ b/exn/src/debug.rs @@ -19,7 +19,7 @@ use std::fmt::Formatter; use crate::Exn; use crate::Frame; -impl fmt::Debug for Exn { +impl fmt::Debug for Exn { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write_exn(f, self.frame(), 0, "") } diff --git a/exn/src/display.rs b/exn/src/display.rs index 80c8875..514a24d 100644 --- a/exn/src/display.rs +++ b/exn/src/display.rs @@ -17,7 +17,7 @@ use std::fmt; use crate::Exn; -impl fmt::Display for Exn { +impl fmt::Display for Exn { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.error()) } diff --git a/exn/src/ext.rs b/exn/src/ext.rs new file mode 100644 index 0000000..fd61228 --- /dev/null +++ b/exn/src/ext.rs @@ -0,0 +1,53 @@ +// Copyright 2025 FastLabs Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::error::Error; + +use crate::Exn; +use crate::Result; + +/// Equivalent to `Ok::<_, Exn>(value)`. +/// +/// This simplifies creation of an `exn::Result` in places where type inference cannot deduce the +/// `E` type of the result — without needing to write `Ok::<_, Exn>(value)`. +/// +/// One might think that `exn::Result::Ok(value)` would work in such cases, but it does not. +/// +/// ```console +/// error[E0282]: type annotations needed for `std::result::Result` +/// --> src/main.rs:11:13 +/// | +/// 11 | let _ = exn::Result::Ok(1); +/// | - ^^^^^^^^^^^^^^^ cannot infer type for type parameter `E` declared on the enum `Result` +/// | | +/// | consider giving this pattern the explicit type `std::result::Result`, where the type parameter `E` is specified +/// ``` +#[expect(non_snake_case)] +pub fn Ok(value: T) -> Result { + Result::Ok(value) +} + +/// An extension trait for error types to raise them as exceptions. +pub trait ErrorExt: Error + Send + Sync + 'static { + /// Raise this error as a new exception. + #[track_caller] + fn raise(self) -> Exn + where + Self: Sized, + { + Exn::new(self) + } +} + +impl ErrorExt for T where T: Error + Send + Sync + 'static {} diff --git a/exn/src/impls.rs b/exn/src/impls.rs index 43ed0cd..bb03769 100644 --- a/exn/src/impls.rs +++ b/exn/src/impls.rs @@ -18,20 +18,20 @@ use std::marker::PhantomData; use std::panic::Location; /// An exception type that can hold an error tree and additional context. -pub struct Exn { +pub struct Exn { // trade one more indirection for less stack size frame: Box, phantom: PhantomData, } -impl From for Exn { +impl From for Exn { #[track_caller] fn from(error: E) -> Self { Exn::new(error) } } -impl Exn { +impl Exn { /// Create a new exception with the given error. /// /// This will automatically walk the [source chain of the error] and add them as children @@ -87,7 +87,7 @@ impl Exn { #[track_caller] pub fn from_iter(children: I, err: E) -> Self where - T: Error + 'static, + T: Error + Send + Sync + 'static, I: IntoIterator, I::Item: Into>, { @@ -101,7 +101,7 @@ impl Exn { /// Raise a new exception; this will make the current exception a child of the new one. #[track_caller] - pub fn raise(self, err: T) -> Exn { + pub fn raise(self, err: T) -> Exn { let mut new_exn = Exn::new(err); new_exn.frame.children.push(*self.frame); new_exn @@ -124,7 +124,7 @@ impl Exn { /// A frame in the exception tree. pub struct Frame { /// The error that occurred at this frame. - error: Box, + error: Box, /// The source code location where this exception frame was created. location: &'static Location<'static>, /// Child exception frames that provide additional context or source errors. @@ -133,7 +133,7 @@ pub struct Frame { impl Frame { /// Return the error that occurred at this frame. - pub fn error(&self) -> &(dyn Error + 'static) { + pub fn error(&self) -> &(dyn Error + Send + Sync + 'static) { &*self.error } diff --git a/exn/src/lib.rs b/exn/src/lib.rs index 46ecec8..6896ab1 100644 --- a/exn/src/lib.rs +++ b/exn/src/lib.rs @@ -77,34 +77,16 @@ mod debug; mod display; +mod ext; mod impls; mod macros; mod option; mod result; +pub use self::ext::ErrorExt; +pub use self::ext::Ok; pub use self::impls::Exn; pub use self::impls::Frame; pub use self::option::OptionExt; pub use self::result::Result; pub use self::result::ResultExt; - -/// Equivalent to `Ok::<_, Exn>(value)`. -/// -/// This simplifies creation of an `exn::Result` in places where type inference cannot deduce the -/// `E` type of the result — without needing to write `Ok::<_, Exn>(value)`. -/// -/// One might think that `exn::Result::Ok(value)` would work in such cases, but it does not. -/// -/// ```console -/// error[E0282]: type annotations needed for `std::result::Result` -/// --> src/main.rs:11:13 -/// | -/// 11 | let _ = exn::Result::Ok(1); -/// | - ^^^^^^^^^^^^^^^ cannot infer type for type parameter `E` declared on the enum `Result` -/// | | -/// | consider giving this pattern the explicit type `std::result::Result`, where the type parameter `E` is specified -/// ``` -#[expect(non_snake_case)] -pub fn Ok(value: T) -> Result { - Result::Ok(value) -} diff --git a/exn/src/option.rs b/exn/src/option.rs index e9f589c..d4450ca 100644 --- a/exn/src/option.rs +++ b/exn/src/option.rs @@ -25,7 +25,7 @@ pub trait OptionExt { /// Construct a new [`Exn`] on the `None` variant. fn ok_or_raise(self, err: F) -> Result where - A: Error, + A: Error + Send + Sync + 'static, F: FnOnce() -> A; } @@ -35,7 +35,7 @@ impl OptionExt for Option { #[track_caller] fn ok_or_raise(self, err: F) -> Result where - A: Error, + A: Error + Send + Sync + 'static, F: FnOnce() -> A, { match self { diff --git a/exn/src/result.rs b/exn/src/result.rs index 8b0f5e6..9a01714 100644 --- a/exn/src/result.rs +++ b/exn/src/result.rs @@ -25,20 +25,20 @@ pub trait ResultExt { type Success; /// The `Err` type that would be wrapped in an [`Exn`]. - type Error: Error + 'static; + type Error: Error + Send + Sync + 'static; /// Raise a new exception on the [`Exn`] inside the [`Result`]. /// /// Apply [`Exn::raise`] on the `Err` variant, refer to it for more information. fn or_raise(self, err: F) -> Result where - A: Error, + A: Error + Send + Sync + 'static, F: FnOnce() -> A; } impl ResultExt for std::result::Result where - E: Error + 'static, + E: Error + Send + Sync + 'static, { type Success = T; type Error = E; @@ -46,7 +46,7 @@ where #[track_caller] fn or_raise(self, err: F) -> Result where - A: Error + 'static, + A: Error + Send + Sync + 'static, F: FnOnce() -> A, { match self { @@ -58,7 +58,7 @@ where impl ResultExt for std::result::Result> where - E: Error, + E: Error + Send + Sync + 'static, { type Success = T; type Error = E; @@ -66,7 +66,7 @@ where #[track_caller] fn or_raise(self, err: F) -> Result where - A: Error + 'static, + A: Error + Send + Sync + 'static, F: FnOnce() -> A, { match self { From 6646b5e2b77f9954dc48437e2933a85eb1021a90 Mon Sep 17 00:00:00 2001 From: tison Date: Fri, 16 Jan 2026 15:11:29 +0800 Subject: [PATCH 2/2] fixup Signed-off-by: tison --- exn/tests/simple.rs | 13 +++++----- exn/tests/snapshots/simple__bail.snap | 2 +- exn/tests/snapshots/simple__ensure_fail.snap | 2 +- .../simple__error_straightforward.snap | 10 ++++---- exn/tests/snapshots/simple__error_tree.snap | 24 +++++++++---------- exn/tests/snapshots/simple__from_error.snap | 2 +- exn/tests/snapshots/simple__option_ext.snap | 2 +- exn/tests/snapshots/simple__result_ext.snap | 4 ++-- 8 files changed, 30 insertions(+), 29 deletions(-) diff --git a/exn/tests/simple.rs b/exn/tests/simple.rs index efde406..7686661 100644 --- a/exn/tests/simple.rs +++ b/exn/tests/simple.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use exn::ErrorExt; use exn::Exn; use exn::OptionExt; use exn::ResultExt; @@ -29,7 +30,7 @@ impl std::error::Error for SimpleError {} #[test] fn test_error_straightforward() { - let e1 = Exn::new(SimpleError("E1")); + let e1 = SimpleError("E1").raise(); let e2 = e1.raise(SimpleError("E2")); let e3 = e2.raise(SimpleError("E3")); let e4 = e3.raise(SimpleError("E4")); @@ -39,21 +40,21 @@ fn test_error_straightforward() { #[test] fn test_error_tree() { - let e1 = Exn::new(SimpleError("E1")); + let e1 = SimpleError("E1").raise(); let e3 = e1.raise(SimpleError("E3")); - let e9 = Exn::new(SimpleError("E9")); + let e9 = SimpleError("E9").raise(); let e10 = e9.raise(SimpleError("E10")); - let e11 = Exn::new(SimpleError("E11")); + let e11 = SimpleError("E11").raise(); let e12 = e11.raise(SimpleError("E12")); let e5 = Exn::from_iter([e3, e10, e12], SimpleError("E5")); - let e2 = Exn::new(SimpleError("E2")); + let e2 = SimpleError("E2").raise(); let e4 = e2.raise(SimpleError("E4")); - let e7 = Exn::new(SimpleError("E7")); + let e7 = SimpleError("E7").raise(); let e8 = e7.raise(SimpleError("E8")); let e6 = Exn::from_iter([e5, e4, e8], SimpleError("E6")); diff --git a/exn/tests/snapshots/simple__bail.snap b/exn/tests/snapshots/simple__bail.snap index 02b6606..c34b677 100644 --- a/exn/tests/snapshots/simple__bail.snap +++ b/exn/tests/snapshots/simple__bail.snap @@ -2,4 +2,4 @@ source: exn/tests/simple.rs expression: result.unwrap_err() --- -An error, at exn/tests/simple.rs:91:9 +An error, at exn/tests/simple.rs:92:9 diff --git a/exn/tests/snapshots/simple__ensure_fail.snap b/exn/tests/snapshots/simple__ensure_fail.snap index ee8f606..e159214 100644 --- a/exn/tests/snapshots/simple__ensure_fail.snap +++ b/exn/tests/snapshots/simple__ensure_fail.snap @@ -2,4 +2,4 @@ source: exn/tests/simple.rs expression: result.unwrap_err() --- -An error, at exn/tests/simple.rs:111:9 +An error, at exn/tests/simple.rs:112:9 diff --git a/exn/tests/snapshots/simple__error_straightforward.snap b/exn/tests/snapshots/simple__error_straightforward.snap index d9dadc6..e772fed 100644 --- a/exn/tests/snapshots/simple__error_straightforward.snap +++ b/exn/tests/snapshots/simple__error_straightforward.snap @@ -2,12 +2,12 @@ source: exn/tests/simple.rs expression: e5 --- -E5, at exn/tests/simple.rs:36:17 +E5, at exn/tests/simple.rs:37:17 | -|-> E4, at exn/tests/simple.rs:35:17 +|-> E4, at exn/tests/simple.rs:36:17 | -|-> E3, at exn/tests/simple.rs:34:17 +|-> E3, at exn/tests/simple.rs:35:17 | -|-> E2, at exn/tests/simple.rs:33:17 +|-> E2, at exn/tests/simple.rs:34:17 | -|-> E1, at exn/tests/simple.rs:32:14 +|-> E1, at exn/tests/simple.rs:33:32 diff --git a/exn/tests/snapshots/simple__error_tree.snap b/exn/tests/snapshots/simple__error_tree.snap index 6e93ff9..943f4ce 100644 --- a/exn/tests/snapshots/simple__error_tree.snap +++ b/exn/tests/snapshots/simple__error_tree.snap @@ -2,26 +2,26 @@ source: exn/tests/simple.rs expression: e6 --- -E6, at exn/tests/simple.rs:59:14 +E6, at exn/tests/simple.rs:60:14 | -|-> E5, at exn/tests/simple.rs:51:14 +|-> E5, at exn/tests/simple.rs:52:14 | | -| |-> E3, at exn/tests/simple.rs:43:17 +| |-> E3, at exn/tests/simple.rs:44:17 | | | -| | |-> E1, at exn/tests/simple.rs:42:14 +| | |-> E1, at exn/tests/simple.rs:43:32 | | -| |-> E10, at exn/tests/simple.rs:46:18 +| |-> E10, at exn/tests/simple.rs:47:18 | | | -| | |-> E9, at exn/tests/simple.rs:45:14 +| | |-> E9, at exn/tests/simple.rs:46:32 | | -| |-> E12, at exn/tests/simple.rs:49:19 +| |-> E12, at exn/tests/simple.rs:50:19 | | -| |-> E11, at exn/tests/simple.rs:48:15 +| |-> E11, at exn/tests/simple.rs:49:34 | -|-> E4, at exn/tests/simple.rs:54:17 +|-> E4, at exn/tests/simple.rs:55:17 | | -| |-> E2, at exn/tests/simple.rs:53:14 +| |-> E2, at exn/tests/simple.rs:54:32 | -|-> E8, at exn/tests/simple.rs:57:17 +|-> E8, at exn/tests/simple.rs:58:17 | - |-> E7, at exn/tests/simple.rs:56:14 + |-> E7, at exn/tests/simple.rs:57:32 diff --git a/exn/tests/snapshots/simple__from_error.snap b/exn/tests/snapshots/simple__from_error.snap index 765da5f..cb43ac5 100644 --- a/exn/tests/snapshots/simple__from_error.snap +++ b/exn/tests/snapshots/simple__from_error.snap @@ -2,4 +2,4 @@ source: exn/tests/simple.rs expression: result.unwrap_err() --- -An error, at exn/tests/simple.rs:80:9 +An error, at exn/tests/simple.rs:81:9 diff --git a/exn/tests/snapshots/simple__option_ext.snap b/exn/tests/snapshots/simple__option_ext.snap index 8834b0d..d5d3390 100644 --- a/exn/tests/snapshots/simple__option_ext.snap +++ b/exn/tests/snapshots/simple__option_ext.snap @@ -2,4 +2,4 @@ source: exn/tests/simple.rs expression: result.unwrap_err() --- -An error, at exn/tests/simple.rs:73:25 +An error, at exn/tests/simple.rs:74:25 diff --git a/exn/tests/snapshots/simple__result_ext.snap b/exn/tests/snapshots/simple__result_ext.snap index 85b189b..013566e 100644 --- a/exn/tests/snapshots/simple__result_ext.snap +++ b/exn/tests/snapshots/simple__result_ext.snap @@ -2,6 +2,6 @@ source: exn/tests/simple.rs expression: result.unwrap_err() --- -Another error, at exn/tests/simple.rs:66:25 +Another error, at exn/tests/simple.rs:67:25 | -|-> An error, at exn/tests/simple.rs:66:25 +|-> An error, at exn/tests/simple.rs:67:25