1 module vtypechoice.result; 2 3 import core.lifetime : forward; 4 import std.sumtype; 5 6 alias isResult(T) = imported!"std.traits".isInstanceOf!(Result, T); 7 alias isOk(T) = imported!"std.traits".isInstanceOf!(Ok, T); 8 alias isErr(T) = imported!"std.traits".isInstanceOf!(Err, T); 9 10 struct Result(T, E = string) 11 { 12 import core.lifetime : forward; 13 import std.traits : CopyConstness, isInstanceOf, Unqual; 14 import std.meta : AliasSeq, IndexOf = staticIndexOf, Map = staticMap; 15 16 17 alias Ok = .Ok!T; 18 alias Err = .Err!E; 19 20 21 static ok(U)(U ok) 22 { 23 static if (.isOk!U || .isErr!U) 24 { 25 static assert(isResult!T, "Cannot convert Ok!("~U.stringof~") to "~Ok.stringof); 26 // lets perform: Result!(Result!int).ok(Ok!(Ok!int)(Ok!int(3)); 27 static if (.isOk!U) 28 CopyConstness!(U, Result) res = { st: Ok(T.ok(ok.get)) }; 29 else 30 CopyConstness!(U, Result) res = { st: Ok(T.err(ok.get)) }; 31 } 32 else static if (isInstanceOf!(U, CopyConstness!(U, Ok))) 33 CopyConstness!(U, Result) res = { st: ok }; 34 else 35 CopyConstness!(U, Result) res = { st: (CopyConstness!(U, Ok)(ok)) }; 36 37 return res; 38 } 39 40 41 static err(U)(U err) 42 { 43 static if (.isOk!U || .isErr!U) 44 { 45 static assert(isResult!T, "Cannot convert Ok!("~U.stringof~") to "~Ok.stringof); 46 // lets perform: Result!(Result!int).ok(Ok!(Ok!int)(Ok!int(3)); 47 static if (.isOk!U) 48 CopyConstness!(U, Result) res = { st: Ok(T.ok(ok.get)) }; 49 else 50 CopyConstness!(U, Result) res = { st: Ok(T.err(ok.get)) }; 51 } 52 else static if (isInstanceOf!(U, CopyConstness!(U, Err))) 53 CopyConstness!(U, Result) res = { st: err }; 54 else 55 CopyConstness!(U, Result) res = { st: CopyConstness!(U, Err)(err) }; 56 57 return res; 58 } 59 60 61 bool opEquals(in Result rhs) scope const 62 { 63 return st == rhs.st; 64 } 65 66 67 bool opEquals(U)(in U rhs) scope const 68 { 69 // allow comparing only with Ok or Err 70 // turning opEquals into a template forces it to never cast to alias this 71 // since both Ok and Err alias to their template parameter type, 72 // restricting the opEquals' parameter to Ok or Err would not evaluate 73 // correctly if for example Ok!(Ok!T) was provided, as it would cast it 74 // to Ok!T making it the wrong comparison to perform 75 enum errmsg = "Cannot compare "~U.stringof~" with "~Result.stringof; 76 static assert(.isOk!U || .isErr!U, errmsg); 77 78 static if (.isOk!U) 79 { 80 // ensure this is a valid type comparison 81 // fail here for a better error message, otherwise it'll fail with 82 // a generic SumType's message 83 static assert(__traits(compiles, T.init == rhs.get), errmsg); 84 return st.match!( 85 (in Err _) => false, 86 (in T ok) => ok == rhs.get, 87 ); 88 } 89 else static if(.isErr!U) // not needed, but kept for clarity 90 { 91 // ensure this is a valid type comparison 92 // fail here for a better error message, otherwise it'll fail with 93 // a generic SumType's message 94 static assert(__traits(compiles, E.init == rhs.get), errmsg); 95 return st.match!( 96 (in Ok _) => false, 97 (in E err) => err == rhs.get, 98 ); 99 } 100 else static assert(0); // we know this never happens 101 } 102 103 104 // FIXME: prohibit implicit casting 105 auto ref opAssign(U)(U rhs) scope return 106 if (.isOk!U) 107 { 108 enum errmsg = "Cannot assign "~U.stringof~" to "~Result.stringof; 109 static assert(__traits(compiles, () { T ok; ok = rhs.get; }), errmsg); 110 111 st = rhs; 112 113 return this; 114 } 115 116 117 auto ref opAssign(U)(U rhs) scope return 118 if (.isErr!U) 119 { 120 enum errmsg = "Cannot assign "~U.stringof~" to "~Result.stringof; 121 122 static assert(__traits(compiles, () { E err; err = rhs.get; }), errmsg); 123 124 st = rhs; 125 126 return this; 127 } 128 129 130 bool isErr() scope const 131 { 132 return st.match!((in value) => .isErr!(typeof(value))); 133 } 134 135 bool isOk() scope const 136 { 137 return !isErr(); 138 } 139 140 141 // =================== 142 // # Result as range # 143 // =================== 144 145 alias back = front; 146 alias empty = isErr; 147 148 auto ref inout(T) front() inout scope return 149 { 150 return this.unwrap(); 151 } 152 153 alias length = isOk; 154 155 156 alias opDollar(size_t dim : 0) = length; 157 158 ref opIndex() scope return 159 { 160 return this; 161 } 162 163 ref inout(T) opIndex(size_t index) scope inout return 164 in (index < length) 165 { 166 return this.unwrap(); 167 } 168 169 ref opIndex(size_t[2] dim) scope return 170 in (dim[0] <= dim[1]) 171 in (dim[1] <= length) 172 { 173 return this; 174 } 175 176 size_t[2] opSlice(size_t dim : 0)(size_t start, size_t end) scope return 177 { 178 return [start, end]; 179 } 180 181 182 alias popBack = popFront; 183 void popFront() scope { st = Err.init; } 184 185 186 inout(Result) save() scope inout { return this; } 187 188 189 SumType!(Ok, Err) st; 190 alias st this; 191 } 192 193 /// 194 version(vtypechoice_unittest) 195 @safe pure nothrow @nogc unittest 196 { 197 alias R = Result!int; 198 199 assert(R.ok(3) == Ok!int(3)); 200 assert(R.err("failure") == Err!string("failure")); 201 202 assert(R.ok(3).match!( 203 (Err!string) => false, // use Ok/Err to match 204 (int) => true, // or use the underlying type to match 205 )); 206 207 assert(R.init == R.init); 208 assert(R.ok(0) != R.err("")); 209 210 211 static assert(!__traits(compiles, R.init = Ok!(Ok!int).init)); 212 static assert(!__traits(compiles, R.init = Ok!short.init)); // TODO: should this be allowed? 213 214 () @trusted 215 { 216 assert((R.ok(3) = 10.ok) == 10.ok); 217 assert((R.ok(3) = "failure".err) == "failure".err); 218 } 219 (); 220 } 221 222 223 struct Ok(T) 224 { 225 T get; 226 alias get this; 227 } 228 229 auto ok(T, string prettyFun = __PRETTY_FUNCTION__, string funName = __FUNCTION__)(T value) 230 { 231 enum untilFunName = () 232 { 233 for (size_t i; i + funName.length < prettyFun.length; i++) 234 if (prettyFun[i .. i + funName.length] == funName) return i; 235 return 0; 236 } (); 237 238 static if (is(mixin(prettyFun[0 .. untilFunName]) Type == Result!Args, Args...)) 239 return Type.ok(value); 240 else 241 return Ok!T(value); 242 } 243 244 /// 245 version(vtypechoice_unittest) 246 @safe pure nothrow @nogc unittest 247 { 248 static assert(__traits(compiles, () => 10.ok)); 249 static assert(is(typeof(() { return 10.ok; } ()) == Ok!int)); 250 static assert(__traits(compiles, function Result!int() => 10.ok)); 251 } 252 253 254 255 struct Err(T) 256 { 257 T get; 258 alias get this; 259 } 260 261 auto err(E, string prettyFun = __PRETTY_FUNCTION__, string funName = __FUNCTION__)(E value) 262 { 263 enum untilFunName = () 264 { 265 for (size_t i; i + funName.length < prettyFun.length; i++) 266 if (prettyFun[i .. i + funName.length] == funName) return i; 267 return 0; 268 } (); 269 270 static if (is(mixin(prettyFun[0 .. untilFunName]) Type == Result!Args, Args...)) 271 return Type.err(value); 272 else 273 return Err!E(value); 274 } 275 276 /// 277 version(vtypechoice_unittest) 278 @safe pure nothrow @nogc unittest 279 { 280 static assert(__traits(compiles, () => "failure".err)); 281 static assert(is(typeof(() { return "failure".err; } ()) == Err!string)); 282 283 static assert(__traits(compiles, function Result!int() => "failure".err)); 284 } 285 286 287 // ================================== 288 // # Result's functional operations # 289 // ================================== 290 291 auto andThen(alias pred, T, E)(auto ref scope Result!(T, E) result) 292 if (isResult!(typeof(pred(T.init))) && is(typeof(pred(T.init)).Types[1] == Err!E)) 293 { 294 alias Type = imported!"std.traits".TemplateArgsOf!(typeof(pred(T.init)).Types[0]); 295 296 return result.match!( 297 (in Err!E err) => Result!(Type, E).err(err.get), 298 (in T ok) => pred(ok), 299 ); 300 } 301 302 /// 303 version(vtypechoice_unittest) 304 @safe pure nothrow @nogc unittest 305 { 306 alias R = Result!int; 307 assert(R.err("failure").andThen!(i => R.ok(i + 3)) == "failure".err); 308 assert(R.ok(7).andThen!(i => R.ok(i + 3)) == 10.ok); 309 } 310 311 312 auto andThen(alias pred, T, E)(auto ref scope Result!(T, E) result) 313 if (isOk!(typeof(pred(T.init)))) 314 { 315 alias Type = imported!"std.traits".TemplateArgsOf!(typeof(pred(T.init)))[0]; 316 317 return result.match!( 318 (in Err!E err) => Result!(Type, E).err(err.get), 319 (in T ok) { Result!(Type, E) res = { st: (pred(ok)) }; return res; }, ); 320 } 321 322 auto andThen(alias pred, T, E)(auto ref scope Result!(T, E) result) 323 if (is(typeof(pred(T.init)) == Err!E)) 324 { 325 alias Type = T; 326 327 return result.match!( 328 (in Err!E err) => Result!(Type, E).err(err.get), 329 (in T ok) { Result!(Type, E) res = { st: (pred(ok)) }; return res; }, 330 ); 331 } 332 333 /// 334 version(vtypechoice_unittest) 335 @safe pure nothrow @nogc unittest 336 { 337 alias R = Result!int; 338 assert(R.err("failure").andThen!(i => ok(i + 3)) == "failure".err); 339 assert(R.ok(7).andThen!(i => err("failure")) == "failure".err); 340 } 341 342 343 auto andThen(alias pred, T, E)(auto ref scope Result!(T, E) result) 344 if (!isResult!(typeof(pred(T.init))) && !isOk!(typeof(pred(T.init))) && !isErr!(typeof(pred(T.init)))) 345 { 346 alias Type = typeof(pred(T.init)); 347 348 return result.match!( 349 (in Err!E err) => Result!(Type, E).err(err.get), 350 (in T ok) => Result!(Type, E).ok(pred(ok)), 351 ); 352 } 353 354 /// 355 version(vtypechoice_unittest) 356 @safe pure nothrow @nogc unittest 357 { 358 alias R = Result!int; 359 assert(R.err("failure").andThen!(i => i + 3) == "failure".err); 360 assert(R.ok(7).andThen!(i => i + 3) == 10.ok); 361 } 362 363 364 Result!(T, E) flatten(T, E)(auto ref scope Result!(Result!(T, E), E) result) 365 { 366 return result.match!( 367 (in Err!E err) => Result!(T, E).err(err.get), 368 (in ok) => ok, 369 ); 370 } 371 372 /// 373 version(vtypechoice_unittest) 374 @safe pure nothrow @nogc unittest 375 { 376 alias R = Result!(Result!int); 377 static assert(is(typeof(R.init.flatten()) == Result!int)); 378 379 assert(R.ok(3.ok).flatten() == 3.ok); 380 assert(R.err("failure").flatten() == "failure".err); 381 } 382 383 384 template fmap(alias pred) 385 { 386 auto fmap(T, E)(auto ref scope Result!(T, E) result) 387 { 388 alias Type = typeof(pred(T.init)); 389 return result.match!( 390 (scope Err!E err) => Result!(Type, E).err(err.get), 391 (scope T ok) => Result!(Type, E).ok(pred(ok)), 392 ); 393 } 394 } 395 396 /// 397 version(vtypechoice_unittest) 398 @safe pure nothrow @nogc unittest 399 { 400 assert(Result!int.ok(3).fmap!(n => n + 7) == 10.ok); 401 402 import std.algorithm: equal, map; 403 import std.range : only; 404 assert(Result!int.ok(3).only.map!(fmap!(n => n + 7)).equal(10.ok.only)); 405 } 406 407 408 template fmapErr(alias pred) 409 { 410 auto fmapErr(T, E)(auto ref scope Result!(T, E) result) 411 { 412 alias Type = typeof(pred(E.init)); 413 return result.match!( 414 (ref scope Ok!T ok) => Result!(T, Type).ok(ok.get), 415 (ref scope E err) => Result!(T, Type).err(pred(err)), 416 ); 417 } 418 } 419 420 /// 421 version(vtypechoice_unittest) 422 @safe pure nothrow @nogc unittest 423 { 424 assert(Result!int.err("failure").fmapErr!(n => 0) == 0.err); 425 426 import std.algorithm: equal, map; 427 import std.range : only; 428 assert(Result!int.err("failure").only.map!(fmapErr!(n => 0)).equal(0.err.only)); 429 } 430 431 432 template fmapOr(alias pred) 433 { 434 auto fmapOr(U, T, E)(auto ref scope Result!(T, E) result, auto ref scope U other) 435 { 436 // simmulate an implicit cast to pred's return type 437 // if the return type is know to be ulong and an int is provided, 438 // the program would compile and run if U was written as size_t 439 // however, as it isn't, it would fail because !is(size_t : int) 440 // by placing Ok!T first the return type will be pred's return type 441 // forcing other to be implicitly castable to it 442 return result.match!( 443 (ref scope Ok!T ok) => pred(ok.get), 444 (in Err!E) => other, 445 ); 446 } 447 } 448 449 /// 450 version(vtypechoice_unittest) 451 @safe pure nothrow @nogc unittest 452 { 453 // source: https://doc.rust-lang.org/std/result/enum.Result.html#method.map_or 454 auto x = Result!string.ok("foo"); 455 assert(x.fmapOr!(str => str.length)(42) == 3); 456 457 auto y = Result!string.err("bar"); 458 assert(y.fmapOr!(str => str.length)(42) == 42); 459 } 460 461 462 template fmapOrElse(alias pred, alias orElse) 463 { 464 auto fmapOrElse(T, E)(auto ref scope Result!(T, E) result) 465 if (is(typeof(orElse(E.init)) : typeof(pred(T.init)))) 466 { 467 // see: fmapOr 468 return result.match!( 469 (ref scope Ok!T ok) => pred(ok.get), 470 (ref scope Err!E err) => orElse(err.get), 471 ); 472 } 473 } 474 475 /// 476 version(vtypechoice_unittest) 477 @safe pure nothrow @nogc unittest 478 { 479 // source: https://doc.rust-lang.org/std/result/enum.Result.html#method.map_or_else 480 auto k = 21; 481 482 auto x = Result!string.ok("foo"); 483 assert(x.fmapOrElse!(str => str.length, e => k * 2) == 3); 484 485 auto y = Result!string.err("bar"); 486 assert(y.fmapOrElse!(str => str.length, e => k * 2) == 42); 487 } 488 489 490 auto ref handle(alias pred, T, E)(return auto ref scope Result!(T, E) result) 491 { 492 result.match!( 493 (ref scope Err!E) {}, 494 (ref scope T ok) { cast(void) pred(ok); }, 495 ); 496 497 return result; 498 } 499 500 /// 501 version(vtypechoice_unittest) 502 @safe pure nothrow @nogc unittest 503 { 504 assert(Result!int.ok(3).handle!((ref n) => n += 7) == 10.ok); 505 assert(Result!int.ok(3).handle!((ref n) => n + 7) == 3.ok); 506 assert(Result!int.err("failure").handle!((ref n) => n = 0) == "failure".err); 507 } 508 509 510 auto ref handleErr(alias pred, T, E)(return auto ref scope Result!(T, E) result) 511 { 512 // no forward because result is used below 513 // therefore the received result must be passed by ref 514 result.match!( 515 (in Ok!T) {}, 516 (ref scope E err) { cast(void) pred(err); }, 517 ); 518 519 return result; 520 } 521 522 version(vtypechoice_unittest) 523 @safe pure nothrow @nogc unittest 524 { 525 assert(Result!int.err("").handleErr!((ref n) => n = "failure") == "failure".err); 526 assert(Result!int.err("failure").handleErr!(n => n = "") == "failure".err); 527 assert(Result!int.ok(3).handleErr!((ref n) => n = "failure") == 3.ok); 528 } 529 530 531 bool has(T, E)(auto ref scope Result!(T, E) result, in T value) 532 { 533 return result.match!( 534 (in Err!E) => false, 535 (in T ok) => ok == value, 536 ); 537 } 538 539 version(vtypechoice_unittest) 540 @safe pure nothrow @nogc unittest 541 { 542 assert(Result!int.ok(3).has(3)); 543 assert(!Result!int.ok(3).has(0)); 544 assert(!Result!int.err("failure").has(3)); 545 } 546 547 548 bool hasErr(T, E)(auto ref scope Result!(T, E) result, in E value) 549 { 550 return result.match!( 551 (in Ok!T) => false, 552 (in E err) => err == value, 553 ); 554 } 555 556 version(vtypechoice_unittest) 557 @safe pure nothrow @nogc unittest 558 { 559 assert(Result!int.err("failure").hasErr("failure")); 560 assert(!Result!int.err("failure").hasErr("errors")); 561 assert(!Result!int.ok(3).hasErr("failure")); 562 } 563 564 565 ref or(T, E)(return auto ref scope Result!(T, E) result, return auto ref scope Result!(T, E) other) 566 { 567 return result.isOk() ? result : other; 568 } 569 570 /// 571 version(vtypechoice_unittest) 572 // @safe but @trusted due to assign 573 @trusted pure nothrow @nogc unittest 574 { 575 auto a = Result!int.ok(3); 576 auto b = Result!int.err("failure"); 577 assert(a.or(b) is a); 578 assert(b.or(a) is a); 579 580 a = Result!int.err("failure"); 581 b = Result!int.err("late failure"); 582 assert(a.or(b) is b); 583 584 a = Result!int.ok(3); 585 b = Result!int.ok(10); 586 assert(a.or(b) is a); 587 } 588 589 590 auto ref orElse(alias pred, T, E)(return auto ref scope Result!(T, E) result) 591 if (is(typeof(pred(E.init)) == Result!(T, E))) 592 { 593 return result.match!( 594 (in Ok!T) => result, 595 (E err) => pred(err), 596 ); 597 } 598 599 /// 600 version(vtypechoice_unittest) 601 @safe pure nothrow @nogc unittest 602 { 603 // source: https://doc.rust-lang.org/std/result/enum.Result.html#method.or_else 604 alias R = Result!(int, int); 605 606 auto sqr = delegate R(int x) => (x * x).ok; 607 auto keep = delegate R(int x) => x.err; 608 609 assert(R.ok(2).orElse!sqr.orElse!sqr == 2.ok); 610 assert(R.ok(2).orElse!keep.orElse!sqr == 2.ok); 611 assert(R.err(3).orElse!sqr.orElse!keep == 9.ok); 612 assert(R.err(3).orElse!keep.orElse!keep == 3.err); 613 } 614 615 616 auto ref inout(T) unwrap(T, E)(auto ref scope inout Result!(T, E) result) 617 { 618 return result.match!( 619 function ref inout(T) (in inout Err!E) => assert(0), 620 function ref inout(T) (return ref inout(T) value) => value, 621 ); 622 } 623 624 version(vtypechoice_unittest) 625 @safe pure nothrow @nogc unittest 626 { 627 assert(Result!int.ok(3).unwrap() == 3); 628 629 static assert(!__traits(compiles, &Result!int.ok(0).unwrap())); // not an lvalue 630 immutable res = Result!int.ok(0); 631 cast(void)&res.unwrap(); 632 static assert(__traits(compiles, &res.unwrap())); // ok lvalue 633 634 static assert(is(typeof(res.unwrap()) == immutable int)); 635 } 636 637 638 auto ref inout(E) unwrapErr(T, E)(return auto ref scope inout Result!(T, E) result) 639 { 640 641 return result.match!( 642 function ref inout(E) (in inout Ok!T) => assert(0), 643 function ref inout(E) (return ref inout(E) value) => value, 644 ); 645 } 646 647 version(vtypechoice_unittest) 648 @safe pure nothrow @nogc unittest 649 { 650 assert(Result!int.err("failure").unwrapErr() == "failure"); 651 652 immutable res = Result!int.err(""); 653 static assert(__traits(compiles, res.unwrapErr())); 654 655 static assert(is(typeof(res.unwrapErr()) == immutable string)); 656 } 657 658 659 version(vtypechoice_unittest) 660 @safe pure nothrow @nogc unittest 661 { 662 with(Result!(immutable int)) 663 { 664 static assert(!__traits(compiles, &ok(3).unwrap())); // not an lvalue 665 immutable res = ok(3); 666 static assert(__traits(compiles, &res.unwrap())); // ok lvalue 667 668 static assert(is(typeof(ok(3).unwrap()) == immutable int)); 669 static assert(is(typeof(err("").unwrapErr()) == string)); 670 static assert(is(typeof(res.unwrapErr()) == immutable string)); 671 } 672 } 673 674 675 // ===================== 676 // # Result as a range # 677 // ===================== 678 679 version(vtypechoice_unittest) 680 @safe pure nothrow @nogc unittest 681 { 682 static assert(__traits(compiles, () => Result!int.init[0])); 683 684 auto r = Result!int.ok(3); 685 686 assert(r[0] == 3); 687 assert(r[0 .. $] is r); 688 689 690 import std.algorithm.comparison : equal; 691 import std.range : only; 692 693 assert(r.equal(3.only)); 694 assert(r[0 .. $].equal(3.only)); 695 assert(r.length == 1); 696 697 698 import std.algorithm.iteration : map, joiner; 699 700 assert(Result!int.err("").map!(n => n + 1).empty); 701 assert(r.only.joiner.front == 3); 702 }