From f407f57137c52949d6b7b878dca68208f2fa2366 Mon Sep 17 00:00:00 2001 From: andylokandy Date: Sun, 18 Jan 2026 14:24:06 +0800 Subject: [PATCH 1/8] feat: impl std::error::Error for Frame --- examples/Cargo.toml | 4 +++ examples/src/into-error.rs | 72 ++++++++++++++++++++++++++++++++++++++ exn/src/impls.rs | 26 ++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 examples/src/into-error.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index e1d4a81..19b20c0 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -38,6 +38,10 @@ path = "src/downcast.rs" name = "make-error" path = "src/make-error.rs" +[[example]] +name = "into-error" +path = "src/into-error.rs" + [package.metadata.release] release = false diff --git a/examples/src/into-error.rs b/examples/src/into-error.rs new file mode 100644 index 0000000..8bccb75 --- /dev/null +++ b/examples/src/into-error.rs @@ -0,0 +1,72 @@ +// 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. + +//! # Into Error Example - Returning `Result<_, Box>` +//! +//! This example shows a common pattern: +//! - Keep using `exn::Result` internally (typed errors + `or_raise()` context). +//! - At the boundary, convert `Exn` into `Box` by wrapping it in a local type that +//! implements `std::error::Error`. + +use std::error::Error; + +use derive_more::Display; +use exn::Result; +use exn::ResultExt; + +fn main() -> std::result::Result<(), Box> { + app::run()?; + Ok(()) +} + +mod app { + use super::*; + + pub fn run() -> Result<(), AppError> { + let port = config::load_port().or_raise(|| AppError)?; + let _ = port; + Ok(()) + } + + #[derive(Debug, Display)] + #[display("failed to start app")] + pub struct AppError; + impl Error for AppError {} +} + +mod config { + use super::*; + + pub fn load_port() -> Result { + let raw = "not-a-number"; + + let port = raw + .parse::() + .or_raise(|| ConfigError(format!("PORT must be a number; got {raw:?}")))?; + + Ok(port) + } + + #[derive(Debug, Display)] + pub struct ConfigError(String); + impl Error for ConfigError {} +} + +// Output when running `cargo run -p examples --example into-error`: +// +// Error: failed to start app, at examples/src/into-error.rs:37:40 +// | +// |-> PORT must be a number; got "not-a-number", at examples/src/into-error.rs:56:14 +// | +// |-> invalid digit found in string, at examples/src/into-error.rs:56:14 diff --git a/exn/src/impls.rs b/exn/src/impls.rs index b815129..a9ca664 100644 --- a/exn/src/impls.rs +++ b/exn/src/impls.rs @@ -148,3 +148,29 @@ impl Frame { &self.children } } + +impl Error for Frame { + fn source(&self) -> Option<&(dyn Error + 'static)> { + self.children + .first() + .map(|child| child as &(dyn Error + 'static)) + } +} + +impl From> for Box { + fn from(exn: Exn) -> Self { + Box::new(exn.frame) + } +} + +impl From> for Box { + fn from(exn: Exn) -> Self { + Box::new(exn.frame) + } +} + +impl From> for Box { + fn from(exn: Exn) -> Self { + Box::new(exn.frame) + } +} From 148952ef82176befa2be9b13e740e93eeb0d38f1 Mon Sep 17 00:00:00 2001 From: andylokandy Date: Sun, 18 Jan 2026 14:29:28 +0800 Subject: [PATCH 2/8] fix --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + examples/Cargo.toml | 1 + examples/src/into-error.rs | 18 +++++++++++------- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9c9adba..460a695 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,6 +52,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + [[package]] name = "bitflags" version = "2.10.0" @@ -180,6 +186,7 @@ dependencies = [ name = "examples" version = "0.0.0" dependencies = [ + "anyhow", "derive_more", "exn", ] diff --git a/Cargo.toml b/Cargo.toml index 5b4c41e..a70ffc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ repository = "https://github.com/fast/exn" exn = { path = "exn" } # Crates.io dependencies +anyhow = { version = "1.0.100" } clap = { version = "4.5.20", features = ["derive"] } derive_more = { version = "2.1.0", features = ["full"] } insta = { version = "1.45.1" } diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 19b20c0..3b76f4d 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -46,6 +46,7 @@ path = "src/into-error.rs" release = false [dev-dependencies] +anyhow = { workspace = true } derive_more = { workspace = true } exn = { workspace = true } diff --git a/examples/src/into-error.rs b/examples/src/into-error.rs index 8bccb75..7729d2c 100644 --- a/examples/src/into-error.rs +++ b/examples/src/into-error.rs @@ -25,11 +25,15 @@ use derive_more::Display; use exn::Result; use exn::ResultExt; -fn main() -> std::result::Result<(), Box> { - app::run()?; +fn main() -> anyhow::Result<()> { + app::run().map_err(convert_error)?; Ok(()) } +fn convert_error(err: exn::Exn) -> anyhow::Error { + anyhow::Error::from_boxed(Box::::from(err)) +} + mod app { use super::*; @@ -65,8 +69,8 @@ mod config { // Output when running `cargo run -p examples --example into-error`: // -// Error: failed to start app, at examples/src/into-error.rs:37:40 -// | -// |-> PORT must be a number; got "not-a-number", at examples/src/into-error.rs:56:14 -// | -// |-> invalid digit found in string, at examples/src/into-error.rs:56:14 +// Error: failed to start app +// +// Caused by: +// 0: PORT must be a number; got "not-a-number" +// 1: invalid digit found in string From 01337583ae31b6999b44582a792c611ce70d0025 Mon Sep 17 00:00:00 2001 From: andylokandy Date: Sun, 18 Jan 2026 14:32:04 +0800 Subject: [PATCH 3/8] fix --- examples/Cargo.toml | 4 ++-- examples/src/{into-error.rs => anyhow.rs} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename examples/src/{into-error.rs => anyhow.rs} (100%) diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 3b76f4d..c73d57c 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -39,8 +39,8 @@ name = "make-error" path = "src/make-error.rs" [[example]] -name = "into-error" -path = "src/into-error.rs" +name = "anyhow" +path = "src/anyhow.rs" [package.metadata.release] release = false diff --git a/examples/src/into-error.rs b/examples/src/anyhow.rs similarity index 100% rename from examples/src/into-error.rs rename to examples/src/anyhow.rs From e74edc639ce8887a98fe6a7efc34f6f1397f2e3e Mon Sep 17 00:00:00 2001 From: andylokandy Date: Sun, 18 Jan 2026 14:34:02 +0800 Subject: [PATCH 4/8] fix --- examples/src/anyhow.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/src/anyhow.rs b/examples/src/anyhow.rs index 7729d2c..0c2e702 100644 --- a/examples/src/anyhow.rs +++ b/examples/src/anyhow.rs @@ -12,12 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! # Into Error Example - Returning `Result<_, Box>` +//! # Anyhow Interoperate Example - Returning `anyhow::Result<_>` //! //! This example shows a common pattern: -//! - Keep using `exn::Result` internally (typed errors + `or_raise()` context). -//! - At the boundary, convert `Exn` into `Box` by wrapping it in a local type that -//! implements `std::error::Error`. +//! - Using `exn::Result` internally. +//! - At the boundary, convert `Exn` into `anyhow::Error`. use std::error::Error; From 13dc3ae6295d48d171350aeca9c2f9428584f392 Mon Sep 17 00:00:00 2001 From: andylokandy Date: Sun, 18 Jan 2026 14:35:01 +0800 Subject: [PATCH 5/8] fix --- examples/src/anyhow.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/src/anyhow.rs b/examples/src/anyhow.rs index 0c2e702..a9b98b6 100644 --- a/examples/src/anyhow.rs +++ b/examples/src/anyhow.rs @@ -66,7 +66,7 @@ mod config { impl Error for ConfigError {} } -// Output when running `cargo run -p examples --example into-error`: +// Output when running `cargo run -p examples --example anyhow`: // // Error: failed to start app // From 3320628eaca6afaa4d372b7b6b9e6b6dc137707d Mon Sep 17 00:00:00 2001 From: andylokandy Date: Sun, 18 Jan 2026 14:40:39 +0800 Subject: [PATCH 6/8] fix --- examples/src/anyhow.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/src/anyhow.rs b/examples/src/anyhow.rs index a9b98b6..3fbc2d9 100644 --- a/examples/src/anyhow.rs +++ b/examples/src/anyhow.rs @@ -30,7 +30,7 @@ fn main() -> anyhow::Result<()> { } fn convert_error(err: exn::Exn) -> anyhow::Error { - anyhow::Error::from_boxed(Box::::from(err)) + anyhow::Error::from_boxed(err.into()) } mod app { From c85d8d0fd4b26400ba59f0444cfe93239c3699e0 Mon Sep 17 00:00:00 2001 From: andylokandy Date: Sun, 18 Jan 2026 15:24:03 +0800 Subject: [PATCH 7/8] add example --- examples/Cargo.toml | 8 ++- examples/src/{anyhow.rs => into-anyhow.rs} | 2 +- examples/src/into-std-error.rs | 71 ++++++++++++++++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) rename examples/src/{anyhow.rs => into-anyhow.rs} (96%) create mode 100644 examples/src/into-std-error.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index c73d57c..c2a0db3 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -39,8 +39,12 @@ name = "make-error" path = "src/make-error.rs" [[example]] -name = "anyhow" -path = "src/anyhow.rs" +name = "into-anyhow" +path = "src/into-anyhow.rs" + +[[example]] +name = "into-std-error" +path = "src/into-std-error.rs" [package.metadata.release] release = false diff --git a/examples/src/anyhow.rs b/examples/src/into-anyhow.rs similarity index 96% rename from examples/src/anyhow.rs rename to examples/src/into-anyhow.rs index 3fbc2d9..ff9e253 100644 --- a/examples/src/anyhow.rs +++ b/examples/src/into-anyhow.rs @@ -66,7 +66,7 @@ mod config { impl Error for ConfigError {} } -// Output when running `cargo run -p examples --example anyhow`: +// Output when running `cargo run -p examples --example into-anyhow`: // // Error: failed to start app // diff --git a/examples/src/into-std-error.rs b/examples/src/into-std-error.rs new file mode 100644 index 0000000..d76f598 --- /dev/null +++ b/examples/src/into-std-error.rs @@ -0,0 +1,71 @@ +// 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. + +//! # std::error::Error Interoperate Example - Returning `Result<_, Box>` +//! +//! This example shows a common pattern: +//! - Using `exn::Result` internally. +//! - At the boundary, convert `Exn` into `Box`. + +use std::error::Error; + +use derive_more::Display; +use exn::Result; +use exn::ResultExt; + +fn main() -> std::result::Result<(), Box> { + app::run()?; + Ok(()) +} + +mod app { + use super::*; + + pub fn run() -> Result<(), AppError> { + let port = config::load_port().or_raise(|| AppError)?; + let _ = port; + Ok(()) + } + + #[derive(Debug, Display)] + #[display("failed to start app")] + pub struct AppError; + impl Error for AppError {} +} + +mod config { + use super::*; + + pub fn load_port() -> Result { + let raw = "not-a-number"; + + let port = raw + .parse::() + .or_raise(|| ConfigError(format!("PORT must be a number; got {raw:?}")))?; + + Ok(port) + } + + #[derive(Debug, Display)] + pub struct ConfigError(String); + impl Error for ConfigError {} +} + +// Output when running `cargo run -p examples --example into-std-error`: +// +// Error: failed to start app, at examples/src/into-std-error.rs:36:40 +// | +// |-> PORT must be a number; got "not-a-number", at examples/src/into-std-error.rs:55:14 +// | +// |-> invalid digit found in string, at examples/src/into-std-error.rs:55:14 From 81c693b2beda2f16aaac3dec52a920d4ffca42d6 Mon Sep 17 00:00:00 2001 From: andylokandy Date: Sat, 31 Jan 2026 01:35:25 +0800 Subject: [PATCH 8/8] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43d6f08..0601820 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,3 +13,4 @@ All significant changes to this project will be documented in this file. ### New Features * This crate is now `no_std` compatible, while the `alloc` crate is still required for heap allocations. It is worth noting that `no_std` support is a nice-to-have feature, and can be dropped if it blocks other important features in the future. Before 1.0, once `exn` APIs settle down, the decision on whether to keep `no_std` as a promise will be finalized. +* `Frame` now implements `std::error::Error`, and `Exn` can be converted into `Box`.