Skip to content

Instantly share code, notes, and snippets.

@Qqwy
Created August 1, 2023 18:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Qqwy/fc467ab9d897e0026a2cd75247e7aad1 to your computer and use it in GitHub Desktop.
Save Qqwy/fc467ab9d897e0026a2cd75247e7aad1 to your computer and use it in GitHub Desktop.
Generic Error handling using anonymous enums for the error cases. Rust alternative to PureScript's 'VEither'/'VExceptT'.
#![feature(type_name_of_val)]
// Depends on the frunk library, v0.4.1
use frunk::coproduct::{Coproduct, CNil, CoprodUninjector, CoprodInjector};
pub trait Variant {
fn default<A>(self, val: A) -> A
where Self: Sized
{
val
}
}
pub trait Exhausted {
type Success;
fn exhausted(self) -> Self::Success
where Self: Sized;
}
pub trait Handle<A, Index> {
type Success;
type Remainder;
#[must_use = "Handle all cases and terminate using `exhausted` or `default`."]
fn handle(self, fun: impl Fn(A) -> Self::Success) -> Self::Remainder;
}
pub trait Coprodlike {}
impl Coprodlike for CNil {}
impl<H, T> Coprodlike for Coproduct<H, T> {}
pub trait EmptyCoproduct {}
impl EmptyCoproduct for CNil {}
impl<X, Co: EmptyCoproduct> Exhausted for Result<X, Co> {
type Success = X;
fn exhausted(self) -> Self::Success
where Self: Sized {
match self {
Ok(val) => val,
Err(_) => unreachable!(),
}
}
}
impl<X, A, Co, Index> Handle<A, Index> for Result<X, Co>
where
Co: CoprodUninjector<A, Index>,
{
type Success = X;
type Remainder = Result<X, Co::Remainder>;
fn handle(self, fun: impl Fn(A) -> Self::Success) -> Self::Remainder
{
match self {
Ok(val) => Ok(val),
Err(co) => {
match co.uninject() {
Ok(this) => Ok(fun(this)),
Err(rest) => Err(rest),
}
}
}
}
}
trait Extend<B, Index> {
type Success;
type Extended;
fn extend(self, fun: impl Fn(Self::Success) -> Result<Self::Success, B>) -> Self::Extended;
}
impl<X, B, Co, Index> Extend<B, Index> for Result<X, Co>
where
Co: CoproductEmbedder<Coprod!(B, ...Co), Index>
{
type Success = X;
type Extended = Result<X, Coprod!(B, ...Co)>;
fn extend(self, fun: impl Fn(Self::Success) -> Result<Self::Success, B>) -> Self::Extended {
match self {
Ok(val) => {
match fun(val) {
Ok(out) => Ok(out),
Err(prob) => Err(<Coprod!(B, ...Co)>::inject(prob)),
}
},
Err(val) => {
Err(val.embed())
}
}
}
}
use frunk::{Coprod, coproduct::CoproductEmbedder};
#[derive(Debug, Clone)]
pub struct BadNumberError;
type ExampleError<Inner> = Coprod!(BadNumberError, ...Inner);
pub fn example<InnerError, Idx>(val: Result<usize, InnerError>) -> Result<usize, ExampleError<InnerError>>
where InnerError: CoproductEmbedder<Coprod!(BadNumberError, ...InnerError), Idx>
{
val.extend(|x| {
match x {
42 => Err(BadNumberError),
other => Ok(other)
}
})
}
trait FromErr<T, Idx>: Sized {
fn from_err(val: T) -> Self;
}
impl<T, E, Co, Idx> FromErr<E, Idx> for Result<T, Co>
where
Co: CoprodInjector<E, Idx>
{
fn from_err(val: E) -> Self {
Err(Co::inject(val))
}
}
trait IntoErr<Res, Idx>: Sized {
fn into_err(self) -> Res;
}
impl<E, Res, Idx> IntoErr<Res, Idx> for E
where
Res: FromErr<E, Idx>,
{
fn into_err(self) -> Res {
Res::from_err(self)
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use frunk::Coprod;
#[test]
fn basic() {
let val: Result<usize, Coprod!(UserError, InternalError)> = UserError("Wrong Input").into_err();
let res = val
.handle(|x: UserError| {println!("user error: {:?}", x); 10})
.handle(|x: _| {println!("inferred error: {:?} {:?}", std::any::type_name_of_val(&x), x); 10})
.exhausted();
// Since all variants were handled, `res` has type `usize`.
// (If they were not, we'd get a compiler error above)
println!("res: {:?}", std::any::type_name_of_val(&res));
}
#[derive(Debug, Clone)]
struct UserError(&'static str);
#[derive(Debug, Clone)]
struct InternalError(&'static str);
#[test]
fn add_more_errors() {
let val: Result<usize, Coprod!(UserError, InternalError)> = Ok(42);
let res = val.extend(|x| {
match x {
42 => Err(BadNumberError),
other => Ok(other)
}
});
// Now `res` has type `Result<usize, Coprod!(BdNumberError, UserError, InternalError)`
println!("res: {res:?}");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment