-
-
Save yaahc/c9348cca4aa34ba8bc92f41ba543919d to your computer and use it in GitHub Desktop.
proof of concept showing convenient `?` propagation without having a generic `From` impl on the inner error type
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This won't compile by on nightly yet. If you're interested in playing around | |
// with the code check out `rustup-toolchain-install-master` for installing | |
// custom toolchains based on automated rust builds, the SHA for the build I | |
// used in testing is `58f7d3f8ad2b6f1192b6623a4bcd45416dae0d9d`. | |
// | |
// ```console | |
// cargo install rustup-toolchain-install-master | |
// rustup-toolchain-install-master -- 58f7d3f8ad2b6f1192b6623a4bcd45416dae0d9d | |
// rustup override set 58f7d3f8ad2b6f1192b6623a4bcd45416dae0d9d | |
// ``` | |
#![feature(try_trait_v2)] | |
#![feature(termination_trait_lib)] | |
#![feature(never_type)] | |
#![feature(control_flow_enum)] | |
#![feature(box_patterns)] | |
#![feature(exhaustive_patterns)] | |
#![feature(backtrace)] | |
#![feature(error_iter)] | |
use std::backtrace::Backtrace; | |
use std::error::Error; | |
use std::fmt; | |
type BoxError = Box<dyn Error + Send + Sync + 'static>; | |
// ======================================================= | |
#[derive(Debug)] | |
pub struct DynError { | |
error: BoxError, | |
backtrace: Option<Backtrace>, | |
} | |
impl fmt::Display for DynError { | |
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
self.error.fmt(f) | |
} | |
} | |
/// This type _does_ implement error | |
impl Error for DynError { | |
fn source(&self) -> Option<&(dyn Error + 'static)> { | |
self.error.source() | |
} | |
fn backtrace(&self) -> Option<&Backtrace> { | |
self.backtrace | |
.as_ref() | |
.or_else(|| Error::chain(&*self.error).find_map(|error| error.backtrace())) | |
} | |
} | |
impl DynError { | |
fn new<E>(error: E) -> Self | |
where | |
BoxError: From<E>, | |
{ | |
let error = BoxError::from(error); | |
// capture a backtrace if the inner error has not | |
let backtrace = Error::chain(&*error) | |
.all(|error| error.backtrace().is_none()) | |
.then(Backtrace::capture); | |
// This logic is necessary for round tripping through `Result<T, | |
// BoxError>`, as demonstrated in `fn thing_3()` | |
// | |
// This is effectively resolving the "overlap rule" issue with `Box<dyn | |
// Error + ...>` at runtime by always boxing it and then checking if it | |
// shouldn't have after the fact with `downcast`. | |
// | |
// Check if the erased error type is already the type we want | |
match error.downcast::<DynError>() { | |
// If it is use it directly | |
Ok(box error) => error, | |
// otherwise create a new `DynError` to wrap the type erased error | |
Err(error) => DynError { backtrace, error }, | |
} | |
} | |
} | |
// ======================================================= | |
mod report { | |
use super::{BoxError, DynError}; | |
use std::ops::{ControlFlow, FromResidual, Try}; | |
use std::process::Termination; | |
/// Result that always converts error types to an `DynError` | |
pub enum DynResult<T> { | |
Ok(T), | |
Err(DynError), | |
} | |
impl<T> Termination for DynResult<T> { | |
fn report(self) -> i32 { | |
match self { | |
DynResult::Ok(_) => 0, | |
DynResult::Err(error) => { | |
eprintln!("Error: {}", error); | |
eprintln!("Error: {:?}", error); | |
1 | |
} | |
} | |
} | |
} | |
impl<T> Try for DynResult<T> { | |
type Ok = T; | |
type Residual = DynResult<!>; | |
fn from_output(value: T) -> Self { | |
DynResult::Ok(value) | |
} | |
fn branch(self) -> ControlFlow<DynResult<!>, T> { | |
match self { | |
DynResult::Ok(value) => ControlFlow::Continue(value), | |
DynResult::Err(error) => ControlFlow::Break(DynResult::Err(error)), | |
} | |
} | |
} | |
impl<T, E> FromResidual<Result<!, E>> for DynResult<T> | |
where | |
BoxError: From<E>, | |
{ | |
fn from_residual(inner: Result<!, E>) -> Self { | |
let Err(error) = inner; | |
let error = DynError::new(error); | |
DynResult::Err(error) | |
} | |
} | |
impl<T> FromResidual<DynResult<!>> for DynResult<T> { | |
fn from_residual(residual: DynResult<!>) -> Self { | |
let DynResult::Err(error) = residual; | |
DynResult::Err(error) | |
} | |
} | |
impl<T> FromResidual<DynResult<!>> for Result<T, BoxError> { | |
fn from_residual(residual: DynResult<!>) -> Self { | |
let DynResult::Err(error) = residual; | |
let error = BoxError::from(error); | |
Err(error) | |
} | |
} | |
} | |
// ======================================================= | |
use report::DynResult; | |
/// innermost function that uses standard Result | |
fn do_thing() -> Result<(), InnerError> { | |
Err(InnerError) | |
} | |
/// function that converts `InnerError` into a `DynError` via the `Result` | |
/// wrapper` | |
fn do_thing2() -> DynResult<()> { | |
do_thing()?; | |
DynResult::Ok(()) | |
} | |
/// function that round trips the `DynError` through a box error | |
fn do_thing3() -> Result<(), BoxError> { | |
do_thing2()?; | |
Ok(()) | |
} | |
/// main function that returns and reports the error, also implicitly extracts | |
/// the DynError back from the `BoxError` it was wrapped in | |
/// | |
/// # Output | |
/// | |
/// ``` | |
/// ❯ cargo run | |
/// Compiling try-tests v0.1.0 | |
/// Finished dev [unoptimized + debuginfo] target(s) in 0.21s | |
/// Running `target/debug/try-tests` | |
/// Error: fake inner error that gets type erased and wrapped many times | |
/// Error: DynError { error: InnerError, backtrace: Some(<disabled>) } | |
/// ``` | |
fn main() -> DynResult<()> { | |
do_thing3()?; | |
DynResult::Ok(()) | |
} | |
/// random error type | |
#[derive(Debug, thiserror::Error)] | |
#[error("fake inner error that gets type erased and wrapped many times")] | |
struct InnerError; |
bstrie
commented
Mar 17, 2021
•
I'm guessing that would make this not compile with the specific crater build version I mentioned in the top level comment so I'm hesitant to apply this change @bstrie
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment