diff --git a/CHANGELOG.md b/CHANGELOG.md index c703ff7..8657148 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ All significant changes to this project will be documented in this file. ### New Features +* `Exn` now implements `.into_error()`, allowing to recover the top-level error with move semantics. * `Exn` now implements `Deref`, allowing for more ergonomic access to the inner error. * 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`. diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 873bf92..8fe1d4c 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -34,6 +34,10 @@ path = "src/custom-layout.rs" name = "downcast" path = "src/downcast.rs" +[[example]] +name = "into-error" +path = "src/into-error.rs" + [[example]] name = "into-anyhow" path = "src/into-anyhow.rs" diff --git a/examples/src/into-error.rs b/examples/src/into-error.rs new file mode 100644 index 0000000..89966c2 --- /dev/null +++ b/examples/src/into-error.rs @@ -0,0 +1,104 @@ +// 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 - recovering without `Clone` +//! +//! This example shows how to retrieve the internal error type + +use std::error::Error; + +use derive_more::Display; +use exn::Result; +use exn::ResultExt; + +fn main() -> Result<(), MainError> { + app::run("what is my age?".to_string()).or_raise(|| MainError)?; + app::run("what is the answer?".to_string()).or_raise(|| MainError)?; + app::run("who am I?".to_string()).or_raise(|| MainError)?; + Ok(()) +} + +#[derive(Debug, Display)] +#[display("fatal error occurred in application")] +struct MainError; +impl std::error::Error for MainError {} + +mod app { + use super::*; + + pub fn run(question: String) -> Result { + match human::answer(question) { + Err(e) => { + if e.is_partial() { + Ok(e.into_error().partial_data()) + } else { + Err(e.raise(AppError)) + } + } + Ok(v) => Ok(v), + } + } + + #[derive(Debug, Display)] + #[display("could not resolve answer")] + pub struct AppError; + impl std::error::Error for AppError {} +} + +mod human { + use exn::bail; + + use super::*; + + pub fn answer(question: String) -> Result { + if question == "what is my age?" { + return Ok(23); + } else if question == "what is the answer?" { + bail!(HumanError::Partial(42)) + } + bail!(HumanError::Fatal { question }) + } + + #[derive(Debug, Display, PartialEq, Eq)] + pub enum HumanError { + #[display("unanswerable question asked: {question}")] + Fatal { + question: String, + }, + Partial(u64), + } + + impl HumanError { + pub fn is_partial(&self) -> bool { + matches!(self, HumanError::Partial(_)) + } + + pub fn partial_data(self) -> u64 { + match self { + HumanError::Partial(v) => v, + _ => panic!(), + } + } + } + + impl Error for HumanError {} +} + +// Output when running `cargo run --example into-error`: +// +// Error: fatal error occurred in application, at examples/src/into-error.rs:28:39 +// | +// |-> could not resolve answer, at examples/src/into-error.rs:46:27 +// | +// |-> unanswerable question asked: who am I?, at examples/src/into-error.rs:70:9 diff --git a/exn/src/impls.rs b/exn/src/impls.rs index 199c030..be0690d 100644 --- a/exn/src/impls.rs +++ b/exn/src/impls.rs @@ -123,6 +123,11 @@ impl Exn { pub fn frame(&self) -> &Frame { &self.frame } + + /// Extract the top-level error using move semantics + pub fn into_error(self) -> E { + *self.frame.error.downcast().expect("error type must match") + } } impl Deref for Exn