www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Poor man's Result<T,E> implementation

Having taken a bit of time to get more familiar with Rust I 
wondered if we could have something like Rust's algebraic result 
type[1] using Phobos' Algebraic template and started to 
experiment:

--- Usage
enum DivisionError
{
     ZeroDivisor
}

Result!(int, DivisionError) divide(int dividend, int divisor)
{
     mixin Result;
     if (divisor == 0) { return err(DivisionError.ZeroDivisor); }
     return ok(dividend / divisor);
}


// If you statically know that in your specific case the error 
cannot
// happen you can just unwrap the Result to the contained 
successful value
int valid = divide(15, 3).unwrap();
// This will throw an instance of Error, since unwrapping a Result
// that you cannot guarantee to actually be Ok is a logic bug.
int invalid = divide(15, 0).unwrap();

// Do some calculations of which some may fail
auto input = [ tuple( 15, 3),
                tuple( -3, 0),
                tuple(  3, 7)];
auto results = input.map!(i => divide(i.expand));

// Use case 1: Silently ignore failed calculations
results.save
        .filter!(r => r.type == typeid(Ok!int))
        .each!(tryVisit!(
     (Ok!int result) => writeln(result),
     () {}
));

// Use case 2: "Handle" them
results.save
        .each!(Visit!(
     (Ok!int result) => writeln(result),
     (Err!DivisionError err) => writeln("Failed to divide: ", err)
));
---

--- Implementation
struct Ok(T) {
     T v;
     alias v this;
     string toString() { return v.to!string; }
}

struct Err(E) {
     E e;
     alias e this;
     string toString() { return e.to!string; }
}

alias Result(T, E) = Algebraic!(Ok!T, Err!E);

mixin template Result()
{
     alias R = typeof(return);
     alias T = typeof(__traits(getMember, R.AllowedTypes[0], "v"));
     alias E = typeof(__traits(getMember, R.AllowedTypes[1], "e"));

     R ok(T v) { return cast(R) Ok!T(v); }
     R err(E e) { return cast(R) Err!E(e); }
}

 trusted nothrow
auto unwrap(R)(R r)
{
     try {
         return cast(typeof(__traits(getMember, R.AllowedTypes[0], 
"v")))
                r.get!(R.AllowedTypes[0])();
     } catch (VariantException) {
         throw new Error("Attempted to unwrap error");
     } catch (Exception) {
         // VariantN.get is supposed to only throw 
VariantException. Bug?
         assert(false);
     }
}

import std.variant;
import std.conv;
import std.typecons;
import std.algorithm;
---

[1] https://doc.rust-lang.org/std/result/enum.Result.html
Mar 03