Last active
May 1, 2021 22:21
-
-
Save iK4tsu/00cfdc5b8a216ef2f61393645da98bf8 to your computer and use it in GitHub Desktop.
Rust Result type implemented in D
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
module result; | |
import std.algorithm : cumulativeFold, each, map, sum; | |
import std.conv : to; | |
import std.exception : assumeWontThrow; | |
import std.range : chain, enumerate, iota, only, take, walkLength, zip; | |
import std.traits : isInstanceOf, ReturnType, Unqual; | |
import core.memory : pureMalloc; | |
struct Result(OkT, ErrT) | |
if (!is(OkT == void) && !is(ErrT == void)) | |
{ | |
static Result!(OkT, ErrT) Ok(OkT value) | |
{ | |
Result!(OkT, ErrT) res; | |
res._okpayload = ok(value); | |
res._state = State.Ok; | |
return res; | |
} | |
static Result!(OkT, ErrT) Err(ErrT value) | |
{ | |
Result!(OkT, ErrT) res; | |
res._errpayload = err(value); | |
res._state = State.Err; | |
return res; | |
} | |
bool isOk() const { return _state == State.Ok; } | |
bool isErr() const { return !isOk(); } | |
OkT except(in string msg) | |
{ | |
if (isOk()) return okpayload._handle; | |
else failExpect(msg, errpayload.toString()); | |
assert(0); | |
} | |
OkT unwrap() | |
{ | |
if (isOk()) return okpayload._handle; | |
else assert(false, errpayload.toString()); | |
} | |
ErrT exceptErr(in string msg) | |
{ | |
if (isErr()) return errpayload._handle; | |
else failExpect(msg, okpayload.toString()); | |
assert(0); | |
} | |
ErrT unwrapErr() | |
{ | |
if (isErr()) return errpayload._handle; | |
else assert(false, okpayload.toString()); | |
} | |
string toString() | |
{ | |
return isOk | |
? "Ok("~okpayload.toString()~")" | |
: "Err("~errpayload.toString()~")"; | |
} | |
private: | |
noreturn failExpect(in string msg, in string payload) | |
{ | |
auto iter = only(msg, ": ", payload, "\0"); | |
auto to = iter.map!"a.length".cumulativeFold!"a + b"; | |
auto from = 1.iota.chain(to.take(3)); | |
immutable len = iter.map!"a.length".sum; | |
auto str = (() @trusted pure nothrow @nogc => (cast(char*)(pureMalloc(len)))[0 .. len])(); | |
zip(from, to, iter).each!(tp => str[tp[0] .. tp[1]] = tp[2]); | |
// asserts are not supposed to be catched | |
// if one does catch it, then it can lead to undefined behavior | |
// str will leak if this is catched | |
assert(false, str); | |
} | |
union { | |
ok _okpayload; | |
err _errpayload; | |
} | |
struct ok | |
{ | |
string toString()() | |
{ | |
static if (__traits(hasMember, OkT, "toString") && is(Unqual!(ReturnType!(OkT.toString)) == string)) | |
return _handle.toString(); | |
else | |
return _handle.to!string; | |
} | |
OkT _handle; | |
} | |
struct err | |
{ | |
string toString()() | |
{ | |
static if (__traits(hasMember, ErrT, "toString") && is(Unqual!(ReturnType!(ErrT.toString)) == string)) | |
return _handle.toString(); | |
else | |
return _handle.to!string; | |
} | |
ErrT _handle; | |
} | |
enum State | |
{ | |
Ok, | |
Err, | |
} | |
auto okpayload() inout @property { return _okpayload; } | |
auto errpayload() inout @property { return _errpayload; } | |
State _state; | |
} | |
mixin template makeResult() | |
{ | |
static assert (isInstanceOf!(Result, typeof(return))); | |
alias Ok = typeof(return).Ok; | |
alias Err = typeof(return).Err; | |
} | |
version(result_unittest) | |
{ | |
import core.exception : AssertError; | |
import std.exception : assertThrown; | |
import std.typecons : Tuple, tuple; | |
struct NogcToString { | |
@safe pure nothrow @nogc | |
string toString() { return "Foo"; } | |
} | |
} | |
/// Result default behavior assert | |
@safe pure nothrow @nogc | |
unittest | |
{ | |
Result!(string, string) res; | |
assert(res.isOk()); | |
assert(res.unwrap() == ""); | |
assert(res.except("This won't fail") == ""); | |
} | |
/// Function nogc | |
@safe pure nothrow @nogc | |
unittest | |
{ | |
Result!(Tuple!(), string) positive(int i) | |
{ | |
if (i > 0) return Result!(Tuple!(), string).Ok(tuple()); | |
else return Result!(Tuple!(), string).Err("Value was not positive!"); | |
} | |
assert(positive(32).isOk()); | |
assert(positive(32).unwrap() == tuple()); | |
} | |
/// Function assert | |
@trusted pure | |
unittest | |
{ | |
Result!(int, string) positive(int i) | |
{ | |
if (i > 0) return Result!(int, string).Ok(i); | |
else return Result!(int, string).Err("Value was not positive!"); | |
} | |
assert(positive(0).isErr()); | |
assertThrown!AssertError(positive(0).unwrap()); | |
} | |
/// Function with mixin makeResult | |
@safe pure nothrow @nogc | |
unittest | |
{ | |
Result!(NogcToString, string) foo(bool b) | |
{ | |
mixin makeResult; | |
if (b) return Ok(NogcToString()); | |
else return Err("Failed in foo!"); | |
} | |
assert(foo(true).isOk()); | |
assert(foo(true).unwrap() == NogcToString()); | |
} | |
/// Result toString | |
@safe pure | |
unittest | |
{ | |
assert(Result!(int, char)().toString() == "Ok(0)"); | |
assert(Result!(Result!(string, int), size_t)().toString() == "Ok(Ok())"); | |
assert(Result!(int, char).Err(char.init).toString() == "Err(\xff)"); | |
assert(Result!(Result!(string, int), size_t).Ok(Result!(string, int).Err(0)).toString() == "Ok(Err(0))"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment