1 /++ 2 This module is implementing the Expected idiom. 3 4 See the [Andrei Alexandrescu’s talk (Systematic Error Handling in C++](http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C) 5 and [its slides](https://skydrive.live.com/?cid=f1b8ff18a2aec5c5&id=F1B8FF18A2AEC5C5!1158). 6 7 Or more recent ["Expect the Expected"](https://www.youtube.com/watch?v=nVzgkepAg5Y) by Andrei Alexandrescu for further background. 8 9 It is also inspired by C++'s proposed [std::expected](https://wg21.link/p0323) and [Rust's](https://www.rust-lang.org/) [Result](https://doc.rust-lang.org/std/result/). 10 11 Similar work is [expectations](http://code.dlang.org/packages/expectations) by Paul Backus. 12 13 Main differences with that are: 14 15 $(LIST 16 * lightweight, no other external dependencies 17 * allows to use same types for `T` and `E` 18 * allows to define $(LREF Expected) without value (`void` for `T`) 19 * provides facility to change the $(LREF Expected) behavior by custom `Hook` implementation using the Design by introspection. 20 * can enforce result check (with a cost) 21 ) 22 23 Default type for error is `string`, i.e. `Expected!int` is the same as `Expected!(int, string)` 24 25 $(LREF Expected) has customizable behavior with the help of a third type parameter, 26 `Hook`. Depending on what methods `Hook` defines, core operations on the 27 $(LREF Expected) may be verified or completely redefined. 28 If `Hook` defines no method at all and carries no state, there is no change in 29 default behavior. 30 This module provides a few predefined hooks (below) that add useful behavior to 31 $(LREF Expected): 32 33 $(BOOKTABLE , 34 $(TR $(TD $(LREF Abort)) $(TD 35 fails every incorrect operation with a call to `assert(0)`. 36 It is the default third parameter, i.e. $(D Expected!short) is the same as 37 $(D Expected!(short, string, Abort)). 38 )) 39 $(TR $(TD $(LREF Throw)) $(TD 40 fails every incorrect operation by throwing an exception. 41 )) 42 ) 43 44 The hook's members are looked up statically in a Design by Introspection manner 45 and are all optional. The table below illustrates the members that a hook type 46 may define and their influence over the behavior of the `Checked` type using it. 47 In the table, `hook` is an alias for `Hook` if the type `Hook` does not 48 introduce any state, or an object of type `Hook` otherwise. 49 50 $(TABLE_ROWS 51 * + Hook member 52 + Semantics in Expected!(T, E, Hook) 53 * - `enableDefaultConstructor` 54 - If defined, $(LREF Expected) would have enabled or disabled default constructor based on it's `bool` value. Default constructor is disabled by default. `opAssign` for value and error types is generated if default constructor is enabled. 55 * - `enableCopyConstructor` 56 - If defined, $(LREF Expected) would have enabled or disabled copy constructor based on it's `bool` value. It is enabled by default. When disabled, it enables automatic check if the result was checked either for value or error. When not checked it calls $(D hook.onUnchecked) if provided. 57 58 $(NOTE WARNING: As currently it's not possible to change internal state of `const` or `immutable` object, automatic checking would't work on these. Hopefully with `__mutable` proposal..) 59 * - `onAccessEmptyValue` 60 - If value is accessed on unitialized $(LREF Expected) or $(LREF Expected) with error value, $(D hook.onAccessEmptyValue!E(err)) is called. If hook doesn't implement the handler, `T.init` is returned. 61 * - `onAccessEmptyError` 62 - If error is accessed on unitialized $(LREF Expected) or $(LREF Expected) with value, $(D hook.onAccessEmptyError()) is called. If hook doesn't implement the handler, `E.init` is returned. 63 * - `onUnchecked` 64 - If the result of $(LREF Expected) isn't checked, $(D hook.onUnchecked()) is called to handle the error. If hook doesn't implement the handler, assert is thrown. 65 $(NOTE Note that `hook.enableCopyConstructor` must be false for checks to work.) 66 ) 67 68 License: BSL-1.0 69 Author: Tomáš Chaloupka 70 +/ 71 72 //TODO: collect errno function call - see https://dlang.org/phobos/std_exception.html#ErrnoException 73 //TODO: ability to enforce error handling (via refcounted payload) 74 75 module expected; 76 77 /// Basic usage 78 @safe unittest 79 { 80 auto foo(int i) { 81 if (i == 0) return unexpected!int("oops"); 82 return expected(42 / i); 83 } 84 85 auto bar(int i) { 86 if (i == 0) throw new Exception("err"); 87 return i-1; 88 } 89 90 // basic checks 91 assert(foo(2)); 92 assert(foo(2).hasValue); 93 assert(!foo(2).hasError); 94 assert(foo(2).value == 21); 95 96 assert(!foo(0)); 97 assert(!foo(0).hasValue); 98 assert(foo(0).hasError); 99 assert(foo(0).error == "oops"); 100 101 // void result 102 assert(Expected!(void)()); // no error -> success 103 assert(!Expected!(void)().hasError); 104 // assert(unexpected("foo").value); // doesn't have hasValue and value properties 105 106 // expected from throwing function 107 assert(expected!bar(1) == 0); 108 assert(expected!bar(0).error.msg == "err"); 109 110 // orElse 111 assert(foo(2).orElse!(() => 0) == 21); 112 assert(foo(0).orElse(100) == 100); 113 114 // andThen 115 assert(foo(2).andThen(foo(6)) == 7); 116 assert(foo(0).andThen(foo(6)).error == "oops"); 117 118 // map 119 assert(foo(2).map!(a => a*2).map!(a => a - 2) == 40); 120 assert(foo(0).map!(a => a*2).map!(a => a - 2).error == "oops"); 121 122 // mapError 123 assert(foo(0).mapError!(e => "OOPS").error == "OOPS"); 124 assert(foo(2).mapError!(e => "OOPS") == 21); 125 126 // mapOrElse 127 assert(foo(2).mapOrElse!(v => v*2, e => 0) == 42); 128 assert(foo(0).mapOrElse!(v => v*2, e => 0) == 0); 129 } 130 131 version (unittest) { 132 import std.algorithm : reverse; 133 import std.exception : assertThrown; 134 import std.stdio : writeln; 135 } 136 137 @safe: 138 139 /++ 140 `Expected!(T, E)` is a type that represents either success or failure. 141 142 Type `T` is used for success value. 143 If `T` is `void`, then $(LREF Expected) can only hold error value and is considered a success when there is no error value. 144 145 Type `E` is used for error value. 146 The default type for the error value is `string`. 147 148 Default behavior of $(LREF Expected) can be modified by the `Hook` template parameter. 149 150 Params: 151 T = represents the expected value 152 E = represents the reason explaining why it doesn’t contains avalue of type T, that is the unexpected value. 153 Hook = defines the $(LREF Expected) type behavior 154 +/ 155 struct Expected(T, E = string, Hook = Abort) 156 if (!is(E == void)) 157 { 158 import std.functional: forward; 159 import std.meta : AliasSeq, Erase, NoDuplicates; 160 import std.traits: isAssignable, isCopyable, Unqual; 161 import std.typecons : Flag, No, Yes; 162 163 private alias Types = NoDuplicates!(Erase!(void, AliasSeq!(T, E))); 164 165 static foreach (i, T; Types) 166 { 167 /++ 168 Constructs an $(LREF Expected) with value or error based on the tye of the provided. 169 170 In case when `T == E`, it constructs $(LREF Expected) with value. 171 172 In case when `T == void`, it constructs $(LREF Expected) with error value. 173 174 Default constructor (if enabled) initializes $(LREF Expected) to `T.init` value. 175 If `T == void`, it initializes $(LREF Expected) with no error. 176 +/ 177 this()(auto ref T val) 178 { 179 static if (isCopyable!T) storage = Storage(val); 180 else storage = Storage(forward!val); 181 setState!(T, Yes.force)(); 182 } 183 184 static if (isCopyable!T) 185 { 186 /// ditto 187 this()(auto ref const(T) val) const 188 { 189 storage = const(Storage)(val); 190 setState!(T, Yes.force)(); 191 } 192 193 /// ditto 194 this()(auto ref immutable(T) val) immutable 195 { 196 storage = immutable(Storage)(val); 197 setState!(T, Yes.force)(); 198 } 199 } 200 else 201 { 202 @disable this(const(T) val) const; 203 @disable this(immutable(T) val) immutable; 204 } 205 } 206 207 // generate constructor with flag to determine type of value 208 static if (Types.length == 1 && !is(T == void)) 209 { 210 /++ Constructs an $(LREF Expected) with value or error based on the provided flag. 211 This constructor is available only for cases when value and error has the same type, 212 so we can still construct $(LREF Expected) with value or error. 213 214 Params: 215 val = Value to set as value or error 216 success = If `true`, $(LREF Expected) with value is created, $(LREF Expected) with error otherwise. 217 +/ 218 this()(auto ref E val, bool success) 219 { 220 static if (isCopyable!T) storage = Storage(val); 221 else storage = Storage(forward!val); 222 setState!E(success ? State.value : State.error); 223 } 224 225 static if (isCopyable!E) 226 { 227 /// ditto 228 this()(auto ref const(E) val, bool success) const 229 { 230 storage = const(Storage)(val); 231 setState!E(success ? State.value : State.error); 232 } 233 234 /// ditto 235 this()(auto ref immutable(E) val, bool success) immutable 236 { 237 storage = immutable(Storage)(val); 238 setState!E(success ? State.value : State.error); 239 } 240 } 241 else 242 { 243 @disable this(const(E) val, bool success) const; 244 @disable this(immutable(E) val, bool success) immutable; 245 } 246 } 247 248 static if (!is(T == void) && !isDefaultConstructorEnabled!Hook) @disable this(); 249 250 static if (!isCopyConstructorEnabled!Hook) @disable this(this); 251 static if (isChecked!Hook) 252 { 253 ~this() 254 { 255 if (!checked) 256 { 257 static if (hasOnUnchecked!Hook) __traits(getMember, Hook, "onUnchecked")(); 258 else assert(0, "unchecked result"); 259 } 260 } 261 } 262 263 static if (isDefaultConstructorEnabled!Hook) 264 { 265 static foreach (i, CT; Types) 266 { 267 static if (isAssignable!CT) 268 { 269 /++ Assigns a value or error to an $(LREF Expected). 270 271 Note: This is only allowed when default constructor is also enabled. 272 +/ 273 void opAssign()(auto ref CT rhs) 274 { 275 //TODO: Hook to disallow reassign 276 storage = Storage(forward!rhs); 277 setState!CT(); 278 } 279 } 280 } 281 } 282 283 //damn these are ugly :( 284 static if (!isChecked!Hook) { 285 /++ Implicit conversion to bool. 286 Returns: `true` if there is no error set, `false` otherwise. 287 +/ 288 bool opCast(T)() const if (is(T == bool)) { return !this.hasError; } 289 } else { 290 /// ditto 291 bool opCast(T)() if (is(T == bool)) { return !this.hasError; } 292 } 293 294 static if (!is(T == void)) 295 { 296 static if (!isChecked!Hook) { 297 /++ Checks whether this $(LREF Expected) object contains a specific expected value. 298 299 * `opEquals` for the value is available only when `T != void`. 300 * `opEquals` for the error isn't available, use equality test for $(LREF Expected) in that case. 301 +/ 302 bool opEquals()(const auto ref T rhs) const 303 { 304 return hasValue && value == rhs; 305 } 306 } else { 307 /// ditto 308 bool opEquals()(auto ref T rhs) { return hasValue && value == rhs; } 309 } 310 } 311 312 static if (!isChecked!Hook) { 313 /// Checks whether this $(LREF Expected) object and `rhs` contain the same expected value or error value. 314 bool opEquals()(const auto ref Expected!(T, E, Hook) rhs) const 315 { 316 if (state != rhs.state) return false; 317 static if (!is(T == void)) { if (hasValue) return value == rhs.value; } 318 return error == rhs.error; 319 } 320 } else { 321 /// ditto 322 bool opEquals()(auto ref Expected!(T, E, Hook) rhs) 323 { 324 if (state != rhs.state) return false; 325 static if (!is(T == void)) { if (hasValue) return value == rhs.value; } 326 return error == rhs.error; 327 } 328 } 329 330 static if (!isChecked!Hook) { 331 /++ Calculates the hash value of the $(LREF Expected) in a way that iff it has a value, 332 it returns hash of the value. 333 Hash is computed using internal state and storage of the $(LREF Expected) otherwise. 334 +/ 335 size_t toHash()() const nothrow 336 { 337 static if (!is(T == void)) { if (hasValue) return value.hashOf; } 338 return storage.hashOf(state); 339 } 340 } else { 341 /// ditto 342 size_t toHash()() nothrow 343 { 344 static if (!is(T == void)) { if (hasValue) return value.hashOf; } 345 return storage.hashOf(state); 346 } 347 } 348 349 static if (!is(T == void)) 350 { 351 static if (!isChecked!Hook) { 352 /// Checks if $(LREF Expected) has value 353 @property bool hasValue()() const { return state == State.value; } 354 } 355 else { 356 /// ditto 357 @property bool hasValue()() 358 { 359 static if (!isCopyConstructorEnabled!Hook) checked = true; 360 return state == State.value; 361 } 362 } 363 364 static if (!isChecked!Hook) { 365 /++ 366 Returns the expected value if there is one. 367 368 With default `Abort` hook, it asserts when there is no value. 369 It calls hook's `onAccessEmptyValue` otherwise. 370 371 It returns `T.init` when hook doesn't provide `onAccessEmptyValue`. 372 +/ 373 @property auto ref inout(T) value() inout 374 { 375 if (state != State.value) 376 { 377 static if (hasOnAccessEmptyValue!(Hook, E)) 378 __traits(getMember, Hook, "onAccessEmptyValue")(state == State.error ? trustedGetError() : E.init); 379 else return T.init; 380 } 381 return trustedGetValue(); 382 } 383 } else { 384 @property auto ref T value() 385 { 386 static if (!isCopyConstructorEnabled!Hook) checked = true; 387 388 if (state != State.value) 389 { 390 static if (hasOnAccessEmptyValue!(Hook, E)) 391 __traits(getMember, Hook, "onAccessEmptyValue")(state == State.error ? trustedGetError() : E.init); 392 else return T.init; 393 } 394 return trustedGetValue(); 395 } 396 } 397 } 398 399 static if (!isChecked!Hook) { 400 /// Checks if $(LREF Expected) has error 401 @property bool hasError()() const { return state == State.error; } 402 } else { 403 /// ditto 404 @property bool hasError()() 405 { 406 static if (!isCopyConstructorEnabled!Hook) checked = true; 407 return state == State.error; 408 } 409 } 410 411 static if (!isChecked!Hook) { 412 /++ 413 Returns the error value. May only be called when `hasValue` returns `false`. 414 415 If there is no error value, it calls hook's `onAccessEmptyError`. 416 417 It returns `E.init` when hook doesn't provide `onAccessEmptyError`. 418 +/ 419 @property auto ref inout(E) error() inout 420 { 421 if (state != State.error) 422 { 423 static if (hasOnAccessEmptyError!Hook) __traits(getMember, Hook, "onAccessEmptyError")(); 424 else return E.init; 425 } 426 return trustedGetError; 427 } 428 } else { 429 @property auto ref E error() 430 { 431 static if (!isCopyConstructorEnabled!Hook) checked = true; 432 433 if (state != State.error) 434 { 435 static if (hasOnAccessEmptyError!Hook) __traits(getMember, Hook, "onAccessEmptyError")(); 436 else return E.init; 437 } 438 return trustedGetError; 439 } 440 } 441 442 // range interface 443 static if (!is(T == void)) 444 { 445 static if (!isChecked!Hook) { 446 /++ Range interface defined by `empty`, `front`, `popFront`. 447 Yields one value if $(LREF Expected) has value. 448 449 If `T == void`, range interface isn't defined. 450 +/ 451 @property bool empty() const { return state != State.value; } 452 453 /// ditto 454 @property auto ref inout(T) front() inout { return value; } 455 } else { 456 @property bool empty() { checked = true; return state != State.value; } 457 458 /// ditto 459 @property auto ref T front() { return value; } 460 } 461 462 /// ditto 463 void popFront() { state = State.empty; } 464 } 465 466 private: 467 468 //FIXME: can probably be union instead, but that doesn't work well with destructors and copy constructors/postblits 469 //or change it for a couple of pointers and make the Expected payload refcounted 470 //that could be used to enforce result check too 471 struct Storage 472 { 473 Types values; 474 475 // generate storage constructors 476 static foreach (i, CT; Types) 477 { 478 @trusted this()(auto ref CT val) 479 { 480 static if (isCopyable!CT) __traits(getMember, Storage, "values")[i] = val; 481 else __traits(getMember, Storage, "values")[i] = forward!val; 482 } 483 484 static if (isCopyable!CT) 485 { 486 @trusted this()(auto ref const(CT) val) const { __traits(getMember, Storage, "values")[i] = val; } 487 @trusted this()(auto ref immutable(CT) val) immutable { __traits(getMember, Storage, "values")[i] = val; } 488 } 489 else 490 { 491 @disable this(const(CT) val) const; 492 @disable this(immutable(CT) val) immutable; 493 } 494 } 495 } 496 497 //@trusted // needed for union 498 ref inout(E) trustedGetError()() inout 499 { 500 static if (Types.length == 1) return __traits(getMember, storage, "values")[0]; 501 else return __traits(getMember, storage, "values")[1]; 502 } 503 504 static if (!is(T == void)) 505 { 506 //@trusted // needed for union 507 ref inout(T) trustedGetValue()() inout 508 { 509 return __traits(getMember, storage, "values")[0]; 510 } 511 } 512 513 enum State : ubyte { empty, value, error } 514 515 Storage storage; 516 State state = State.empty; 517 static if (!isCopyConstructorEnabled!Hook) bool checked = false; 518 519 void setState(MT, Flag!"force" force = No.force)() 520 { 521 State s; 522 static if (Types.length == 1 && is(T == void)) s = State.error; 523 else static if (Types.length == 1 || is(MT == T)) s = State.value; 524 else s = State.error; 525 526 static if (!force) 527 { 528 //TODO: change with Hook? 529 assert(state == State.empty || state == s, "Can't change meaning of already set Expected type"); 530 } 531 532 state = s; 533 } 534 } 535 536 /// Template to determine if provided Hook enables default constructor for $(LREF Expected) 537 template isDefaultConstructorEnabled(Hook) 538 { 539 static if (__traits(hasMember, Hook, "enableDefaultConstructor")) 540 { 541 static assert( 542 is(typeof(__traits(getMember, Hook, "enableDefaultConstructor")) : bool), 543 "Hook's enableDefaultConstructor is expected to be of type bool" 544 ); 545 static if (__traits(getMember, Hook, "enableDefaultConstructor")) enum isDefaultConstructorEnabled = true; 546 else enum isDefaultConstructorEnabled = false; 547 } 548 else enum isDefaultConstructorEnabled = false; 549 } 550 551 /// 552 unittest 553 { 554 struct Foo {} 555 struct Bar { static immutable bool enableDefaultConstructor = true; } 556 static assert(!isDefaultConstructorEnabled!Foo); 557 static assert(isDefaultConstructorEnabled!Bar); 558 } 559 560 /++ Template to determine if hook provides function called on empty value. 561 +/ 562 template hasOnAccessEmptyValue(Hook, E) 563 { 564 static if (__traits(hasMember, Hook, "onAccessEmptyValue")) 565 { 566 static assert( 567 is(typeof(__traits(getMember, Hook, "onAccessEmptyValue")(E.init))), 568 "Hook's onAccessEmptyValue is expected to be callable with error value type" 569 ); 570 enum hasOnAccessEmptyValue = true; 571 } 572 else enum hasOnAccessEmptyValue = false; 573 } 574 575 /// 576 unittest 577 { 578 struct Foo {} 579 struct Bar { static void onAccessEmptyValue(E)(E err) {} } 580 static assert(!hasOnAccessEmptyValue!(Foo, string)); 581 static assert(hasOnAccessEmptyValue!(Bar, string)); 582 } 583 584 /++ Template to determine if hook provides function called on empty error. 585 +/ 586 template hasOnAccessEmptyError(Hook) 587 { 588 static if (__traits(hasMember, Hook, "onAccessEmptyError")) 589 { 590 static assert( 591 is(typeof(__traits(getMember, Hook, "onAccessEmptyError")())), 592 "Hook's onAccessEmptyValue is expected to be callable with no arguments" 593 ); 594 enum hasOnAccessEmptyError = true; 595 } 596 else enum hasOnAccessEmptyError = false; 597 } 598 599 /// 600 unittest 601 { 602 struct Foo {} 603 struct Bar { static void onAccessEmptyError() {} } 604 static assert(!hasOnAccessEmptyError!Foo); 605 static assert(hasOnAccessEmptyError!Bar); 606 } 607 608 /++ Template to determine if hook enables or disables copy constructor. 609 610 It is enabled by default. 611 612 See $(LREF hasOnUnchecked) handler, which can be used in combination with disabled 613 copy constructor to enforce result check. 614 615 $(WARNING If copy constructor is disabled, it severely limits function chaining 616 as $(LREF Expected) needs to be passed as rvalue in that case.) 617 +/ 618 template isCopyConstructorEnabled(Hook) 619 { 620 static if (__traits(hasMember, Hook, "enableCopyConstructor")) 621 { 622 static assert( 623 is(typeof(__traits(getMember, Hook, "enableCopyConstructor")) : bool), 624 "Hook's enableCopyConstructor is expected to be of type bool" 625 ); 626 static if (__traits(getMember, Hook, "enableCopyConstructor")) enum isCopyConstructorEnabled = true; 627 else enum isCopyConstructorEnabled = false; 628 } 629 else enum isCopyConstructorEnabled = true; 630 } 631 632 /// 633 unittest 634 { 635 struct Foo {} 636 struct Bar { static immutable bool enableCopyConstructor = false; } 637 static assert(isCopyConstructorEnabled!Foo); 638 static assert(!isCopyConstructorEnabled!Bar); 639 } 640 641 // just a helper to determine check behavior 642 private template isChecked(Hook) 643 { 644 enum isChecked = !isCopyConstructorEnabled!Hook; 645 } 646 647 /++ Template to determine if hook provides custom handler for case 648 when the $(LREF Expected) result is not checked. 649 650 For this to work it currently also has to pass $(LREF isCopyConstructorEnabled) 651 as this is implemented by simple flag controled on $(LREF Expected) destructor. 652 +/ 653 template hasOnUnchecked(Hook) 654 { 655 static if (__traits(hasMember, Hook, "onUnchecked")) 656 { 657 static assert( 658 is(typeof(__traits(getMember, Hook, "onUnchecked")())), 659 "Hook's onUnchecked is expected to be callable with no arguments" 660 ); 661 static assert( 662 !isCopyConstructorEnabled!Hook, 663 "For unchecked check to work, it is currently needed to also disable copy constructor" 664 ); 665 enum hasOnUnchecked = true; 666 } 667 else enum hasOnUnchecked = false; 668 } 669 670 /// 671 @system unittest 672 { 673 struct Foo {} 674 struct Bar { static void onUnchecked() { throw new Exception("result unchecked"); } } 675 struct Hook { 676 static immutable bool enableCopyConstructor = false; 677 static void onUnchecked() @safe { throw new Exception("result unchecked"); } 678 } 679 680 // template checks 681 static assert(!hasOnUnchecked!Foo); 682 static assert(!__traits(compiles, hasOnUnchecked!Bar)); // missing disabled constructor 683 static assert(hasOnUnchecked!Hook); 684 685 // copy constructor 686 auto exp = expected!(string, Hook)(42); 687 auto exp2 = unexpected!(int, Hook)("foo"); 688 static assert(!__traits(compiles, exp.andThen(expected!(string, Hook)(42)))); // disabled cc 689 assert(exp.andThen(exp2).error == "foo"); // passed by ref so no this(this) called 690 691 // check for checked result 692 assertThrown({ expected!(string, Hook)(42); }()); 693 assertThrown({ unexpected!(void, Hook)("foo"); }()); 694 } 695 696 /++ Default hook implementation for $(LREF Expected) 697 +/ 698 struct Abort 699 { 700 static: 701 /++ Default constructor for $(LREF Expected) is disabled 702 Same with the `opAssign`, so $(LREF Expected) can be only constructed 703 once and not modified afterwards. 704 +/ 705 immutable bool enableDefaultConstructor = false; 706 707 /// Handler for case when empty value is accessed 708 void onAccessEmptyValue(E)(E err) nothrow @nogc 709 { 710 assert(0, "Can't access value of unexpected"); 711 } 712 713 /// Handler for case when empty error is accessed 714 void onAccessEmptyError() nothrow @nogc 715 { 716 assert(0, "Can't access error on expected value"); 717 } 718 } 719 720 @system unittest 721 { 722 static assert(!isDefaultConstructorEnabled!Abort); 723 static assert(hasOnAccessEmptyValue!(Abort, string)); 724 static assert(hasOnAccessEmptyValue!(Abort, int)); 725 static assert(hasOnAccessEmptyError!Abort); 726 727 assertThrown!Throwable(expected(42).error); 728 assertThrown!Throwable(unexpected!int("foo").value); 729 } 730 731 /++ Hook implementation that throws exceptions instead of default assert behavior. 732 +/ 733 struct Throw 734 { 735 static: 736 737 /++ Default constructor for $(LREF Expected) is disabled 738 Same with the `opAssign`, so $(LREF Expected) can be only constructed 739 once and not modified afterwards. 740 +/ 741 immutable bool enableDefaultConstructor = false; 742 743 /++ Handler for case when empty value is accessed 744 745 Throws: 746 If `E` inherits from `Throwable`, the error value is thrown. 747 Otherwise, an [Unexpected] instance containing the error value is 748 thrown. 749 +/ 750 void onAccessEmptyValue(E)(E err) 751 { 752 static if(is(E : Throwable)) throw err; 753 else throw new Unexpected!E(err); 754 } 755 756 /// Handler for case when empty error is accessed 757 void onAccessEmptyError() 758 { 759 throw new Unexpected!string("Can't access error on expected value"); 760 } 761 } 762 763 unittest 764 { 765 static assert(!isDefaultConstructorEnabled!Throw); 766 static assert(hasOnAccessEmptyValue!(Throw, string)); 767 static assert(hasOnAccessEmptyValue!(Throw, int)); 768 static assert(hasOnAccessEmptyError!Throw); 769 770 assertThrown!(Unexpected!string)(expected!(string, Throw)(42).error); 771 assertThrown!(Unexpected!string)(unexpected!(int, Throw)("foo").value); 772 } 773 774 /++ An exception that represents an error value. 775 776 This is used by $(LREF Throw) hook when undefined value or error is 777 accessed on $(LREF Expected) 778 +/ 779 class Unexpected(T) : Exception 780 { 781 T error; /// error value 782 783 /// Constructs an `Unexpected` exception from an error value. 784 pure @safe @nogc nothrow 785 this(T value, string file = __FILE__, size_t line = __LINE__) 786 { 787 super("Unexpected error", file, line); 788 this.error = error; 789 } 790 } 791 792 /++ 793 Creates an $(LREF Expected) object from an expected value, with type inference. 794 +/ 795 Expected!(T, E, Hook) expected(E = string, Hook = Abort, T)(T value) 796 { 797 return Expected!(T, E, Hook)(value); 798 } 799 800 /// ditto 801 Expected!(void, E, Hook) expected(E = string, Hook = Abort)() 802 { 803 return Expected!(void, E, Hook)(); 804 } 805 806 // expected 807 unittest 808 { 809 // void 810 { 811 auto res = expected(); 812 static assert(is(typeof(res) == Expected!(void, string))); 813 assert(res); 814 } 815 816 // int 817 { 818 auto res = expected(42); 819 static assert(is(typeof(res) == Expected!(int, string))); 820 assert(res); 821 assert(res.value == 42); 822 } 823 824 // string 825 { 826 auto res = expected("42"); 827 static assert(is(typeof(res) == Expected!(string, string))); 828 assert(res); 829 assert(res.value == "42"); 830 } 831 832 // other error type 833 { 834 auto res = expected!bool(42); 835 static assert(is(typeof(res) == Expected!(int, bool))); 836 assert(res); 837 assert(res.value == 42); 838 } 839 } 840 841 /++ Constructs $(LREF Expected) from the result of the provided function. 842 843 If the function is `nothrow`, it just returns it's result using $(LREF Expected). 844 845 If not, then it uses `try catch` block and constructs $(LREF Expected) with value or error. 846 +/ 847 template expected(alias fun, Hook = Abort) 848 { 849 auto expected(Args...)(auto ref Args args) if (is(typeof(fun(args)))) 850 { 851 import std.traits : hasFunctionAttributes; 852 853 alias T = typeof(fun(args)); 854 static if (is(hasFunctionAttributes!(fun, "nothrow"))) return expected!Exception(fun(args)); 855 else 856 { 857 try return Expected!(T, Exception)(fun(args)); 858 catch (Exception ex) return unexpected!T(ex); 859 } 860 } 861 } 862 863 /// 864 unittest 865 { 866 auto fn(int v) { if (v == 42) throw new Exception("don't panic"); return v; } 867 868 assert(expected!fn(1) == 1); 869 assert(expected!fn(42).error.msg == "don't panic"); 870 } 871 872 /++ 873 Creates an $(LREF Expected) object from an error value, with type inference. 874 +/ 875 Expected!(T, E, Hook) unexpected(T = void, Hook = Abort, E)(E err) 876 { 877 static if (Expected!(T, E, Hook).Types.length == 1 && !is(T == void)) 878 return Expected!(T, E, Hook)(err, false); 879 else return Expected!(T, E, Hook)(err); 880 } 881 882 //unexpected 883 unittest 884 { 885 // implicit void value type 886 { 887 auto res = unexpected("foo"); 888 static assert(is(typeof(res) == Expected!(void, string))); 889 assert(!res); 890 assert(res.error == "foo"); 891 } 892 893 // bool 894 { 895 auto res = unexpected!int("42"); 896 static assert(is(typeof(res) == Expected!(int, string))); 897 assert(!res); 898 assert(res.error == "42"); 899 } 900 901 // other error type 902 { 903 auto res = unexpected!bool(42); 904 static assert(is(typeof(res) == Expected!(bool, int))); 905 assert(!res); 906 assert(res.error == 42); 907 } 908 } 909 910 /++ 911 Returns the error contained within the $(LREF Expected) _and then_ another value if there's no error. 912 This function can be used for control flow based on $(LREF Expected) values. 913 914 Params: 915 exp = The $(LREF Expected) to call andThen on 916 value = The value to return if there isn't an error 917 pred = The predicate to call if the there isn't an error 918 +/ 919 auto ref EX andThen(EX)(auto ref EX exp, auto ref EX value) 920 if (is(EX : Expected!(T, E, H), T, E, H)) 921 { 922 return exp.hasError ? exp : value; 923 } 924 925 /// ditto 926 auto ref EX andThen(alias pred, EX)(auto ref EX exp) 927 if ( 928 is(EX : Expected!(T, E, H), T, E, H) 929 && is(typeof(pred()) : EX) 930 ) 931 { 932 return exp.hasError ? exp : pred(); 933 } 934 935 /// 936 unittest 937 { 938 assert(expected(42).andThen(expected(1)) == 1); 939 assert(expected(42).andThen!(() => expected(0)) == 0); 940 assert(expected(42).andThen(unexpected!int("foo")).error == "foo"); 941 assert(expected(42).andThen!(() => unexpected!int("foo")).error == "foo"); 942 assert(unexpected!int("foo").andThen(expected(42)).error == "foo"); 943 assert(unexpected!int("foo").andThen!(() => expected(42)).error == "foo"); 944 assert(unexpected!int("foo").andThen(unexpected!int("bar")).error == "foo"); 945 assert(unexpected!int("foo").andThen!(() => unexpected!int("bar")).error == "foo"); 946 947 // with void value 948 assert(expected().andThen!(() => expected())); 949 assert(expected().andThen!(() => unexpected("foo")).error == "foo"); 950 assert(unexpected("foo").andThen!(() => expected()).error == "foo"); 951 } 952 953 /++ 954 Returns the value contained within the $(LREF Expected) _or else_ another value if there's an error. 955 This function can be used for control flow based on $(LREF Expected) values. 956 957 Params: 958 exp = The $(LREF Expected) to call orElse on 959 value = The value to return if there is an error 960 pred = The predicate to call if the there is an error 961 +/ 962 U orElse(EX, U)(auto ref EX exp, lazy U value) 963 if (is(EX : Expected!(T, E, H), T, E, H) && is(U : T)) 964 { 965 return exp.orElse!value; 966 } 967 968 /// ditto 969 auto ref orElse(alias pred, EX)(auto ref EX exp) 970 if (is(EX : Expected!(T, E, H), T, E, H) && is(typeof(pred()) : T)) 971 { 972 return exp.hasError ? pred() : exp.value; 973 } 974 975 /// ditto 976 auto ref orElse(alias pred, EX)(auto ref EX exp) 977 if ( 978 is(EX : Expected!(T, E, H), T, E, H) 979 && is(typeof(pred()) : Expected!(T, E, H)) 980 ) 981 { 982 return exp.hasError ? pred() : exp; 983 } 984 985 /// 986 unittest 987 { 988 assert(expected(42).orElse(0) == 42); 989 assert(expected(42).orElse!(() => 0) == 42); 990 assert(unexpected!int("foo").orElse(0) == 0); 991 assert(unexpected!int("foo").orElse!(() => 0) == 0); 992 assert(expected(42).orElse!(() => expected(0)) == 42); 993 assert(unexpected!int("foo").orElse!(() => expected(42)) == 42); 994 assert(unexpected!int("foo").orElse!(() => unexpected!int("bar")).error == "bar"); 995 996 // with void value 997 assert(expected().orElse!(() => unexpected("foo"))); 998 assert(unexpected("foo").orElse!(() => expected())); 999 assert(unexpected("foo").orElse!(() => unexpected("bar")).error == "bar"); 1000 } 1001 1002 /++ 1003 Applies a function to the expected value in an $(LREF Expected) object. 1004 1005 If no expected value is present, the original error value is passed through 1006 unchanged, and the function is not called. 1007 1008 Params: 1009 op = function called to map $(LREF Expected) value 1010 hook = use another hook for mapped $(LREF Expected) 1011 1012 Returns: 1013 A new $(LREF Expected) object containing the result. 1014 +/ 1015 template map(alias op, Hook = Abort) 1016 { 1017 /++ 1018 The actual `map` function. 1019 1020 Params: 1021 self = an [Expected] object 1022 +/ 1023 auto map(T, E, H)(auto ref Expected!(T, E, H) self) 1024 if ((is(T == void) && is(typeof(op()))) || (!is(T == void) && is(typeof(op(self.value))))) 1025 { 1026 static if (is(T == void)) alias U = typeof(op()); 1027 else alias U = typeof(op(self.value)); 1028 1029 if (self.hasError) return unexpected!(U, Hook)(self.error); 1030 else 1031 { 1032 static if (is(T == void)) return expected!(E, Hook)(op()); 1033 else return expected!(E, Hook)(op(self.value)); 1034 } 1035 } 1036 } 1037 1038 /// 1039 unittest 1040 { 1041 { 1042 assert(expected(42).map!(a => a/2).value == 21); 1043 assert(expected().map!(() => 42).value == 42); 1044 assert(unexpected!int("foo").map!(a => 42).error == "foo"); 1045 assert(unexpected("foo").map!(() => 42).error == "foo"); 1046 } 1047 1048 // remap hook 1049 { 1050 static struct Hook {} 1051 auto res = expected(42).map!(a => a/2, Hook); 1052 assert(res == 21); 1053 static assert(is(typeof(res) == Expected!(int, string, Hook))); 1054 } 1055 } 1056 1057 /++ 1058 Applies a function to the expected error in an $(LREF Expected) object. 1059 1060 If no error is present, the original value is passed through 1061 unchanged, and the function is not called. 1062 1063 Params: 1064 op = function called to map $(LREF Expected) error 1065 hook = use another hook for mapped $(LREF Expected) 1066 1067 Returns: 1068 A new $(LREF Expected) object containing the result. 1069 +/ 1070 template mapError(alias op, Hook = Abort) 1071 { 1072 /++ 1073 The actual `mapError` function. 1074 1075 Params: 1076 self = an [Expected] object 1077 +/ 1078 auto mapError(T, E, H)(auto ref Expected!(T, E, H) self) 1079 if (is(typeof(op(self.error)))) 1080 { 1081 alias U = typeof(op(self.error)); 1082 1083 static if (!is(T == void)) 1084 { 1085 if (self.hasValue) return expected!(U, Hook)(self.value); 1086 } 1087 return unexpected!(T, Hook)(op(self.error)); 1088 } 1089 } 1090 1091 /// 1092 unittest 1093 { 1094 { 1095 assert(expected(42).mapError!(e => e).value == 42); 1096 assert(unexpected("foo").mapError!(e => 42).error == 42); 1097 assert(unexpected("foo").mapError!(e => new Exception(e)).error.msg == "foo"); 1098 } 1099 1100 // remap hook 1101 { 1102 static struct Hook {} 1103 auto res = expected(42).mapError!(e => e, Hook); 1104 assert(res == 42); 1105 static assert(is(typeof(res) == Expected!(int, string, Hook))); 1106 1107 auto res2 = unexpected!int("foo").mapError!(e => "bar", Hook); 1108 assert(res2.error == "bar"); 1109 static assert(is(typeof(res2) == Expected!(int, string, Hook))); 1110 } 1111 } 1112 1113 /++ 1114 Maps a `Expected<T, E>` to `U` by applying a function to a contained value, or a fallback function to a contained error value. 1115 1116 Both functions has to be of the same return type. 1117 1118 This function can be used to unpack a successful result while handling an error. 1119 1120 Params: 1121 valueOp = function called to map $(LREF Expected) value 1122 errorOp = function called to map $(LREF Expected) error 1123 hook = use another hook for mapped $(LREF Expected) 1124 1125 Returns: 1126 A new $(LREF Expected) object containing the result. 1127 +/ 1128 template mapOrElse(alias valueOp, alias errorOp) 1129 { 1130 /++ 1131 The actual `mapOrElse` function. 1132 1133 Params: 1134 self = an [Expected] object 1135 +/ 1136 auto mapOrElse(T, E, H)(auto ref Expected!(T, E, H) self) 1137 if ( 1138 is(typeof(errorOp(self.error))) && 1139 ( 1140 (is(T == void) && is(typeof(valueOp()) == typeof(errorOp(self.error)))) || 1141 (!is(T == void) && is(typeof(valueOp(self.value)) == typeof(errorOp(self.error)))) 1142 ) 1143 ) 1144 { 1145 alias U = typeof(errorOp(self.error)); 1146 1147 if (self.hasError) return errorOp(self.error); 1148 else 1149 { 1150 static if (is(T == void)) return valueOp(); 1151 else return valueOp(self.value); 1152 } 1153 } 1154 } 1155 1156 /// 1157 unittest 1158 { 1159 assert(expected(42).mapOrElse!(v => v/2, e => 0) == 21); 1160 assert(expected().mapOrElse!(() => true, e => false)); 1161 assert(unexpected!int("foo").mapOrElse!(v => v/2, e => 42) == 42); 1162 assert(!unexpected("foo").mapOrElse!(() => true, e => false)); 1163 } 1164 1165 // -- global tests -- 1166 1167 // Expected.init 1168 @system nothrow unittest 1169 { 1170 struct EnableDefaultConstructor { static immutable bool enableDefaultConstructor = true; } 1171 1172 { 1173 auto res = Expected!(int, string).init; 1174 assert(!res.hasValue && !res.hasError); 1175 assert(res); 1176 assertThrown!Throwable(res.value); 1177 assertThrown!Throwable(res.error); 1178 static assert(!__traits(compiles, res = 42)); 1179 } 1180 1181 { 1182 auto res = Expected!(int, string, EnableDefaultConstructor).init; 1183 assert(!res.hasValue && !res.hasError); 1184 assert(res); 1185 assert(res.value == 0); 1186 assert(res.error is null); 1187 res = 42; 1188 assert(res.value == 42); 1189 } 1190 1191 // T == void 1192 { 1193 auto res = Expected!(void, string).init; 1194 static assert(!__traits(compiles, res.hasValue)); 1195 static assert(!__traits(compiles, res.value)); 1196 static assert(!__traits(compiles, res = "foo")); 1197 assert(!res.hasError); 1198 assert(res); 1199 assertThrown!Throwable(res.error); 1200 } 1201 1202 // T == void 1203 { 1204 auto res = Expected!(void, string, EnableDefaultConstructor).init; 1205 static assert(!__traits(compiles, res.hasValue)); 1206 static assert(!__traits(compiles, res.value)); 1207 assert(!res.hasError); 1208 assert(res.state == Expected!(void, string, EnableDefaultConstructor).State.empty); 1209 assert(res); 1210 assert(res.error is null); 1211 res = "foo"; 1212 assert(res.error == "foo"); 1213 } 1214 } 1215 1216 // Default constructor - disabled 1217 unittest 1218 { 1219 static assert(!__traits(compiles, Expected!(int, string)())); 1220 } 1221 1222 // Default constructor - enabled 1223 @system nothrow unittest 1224 { 1225 struct EnableDefaultConstructor { static immutable bool enableDefaultConstructor = true; } 1226 { 1227 auto res = Expected!(int, string, EnableDefaultConstructor)(); 1228 assert(!res.hasValue && !res.hasError); 1229 assert(res); 1230 assert(res.value == 0); 1231 assert(res.error is null); 1232 res = 42; 1233 assert(res); 1234 assert(res.value == 42); 1235 } 1236 1237 { 1238 auto res = Expected!(void, string, EnableDefaultConstructor)(); 1239 assert(!res.hasError); 1240 assert(res); 1241 assert(res.error is null); 1242 res = "foo"; 1243 assert(res.hasError); 1244 assert(!res); 1245 assert(res.error == "foo"); 1246 } 1247 } 1248 1249 // Default types 1250 nothrow @nogc unittest 1251 { 1252 auto res = Expected!(int)(42); 1253 assert(res); 1254 assert(res.hasValue && !res.hasError); 1255 assert(res.value == 42); 1256 res.value = 43; 1257 assert(res.value == 43); 1258 } 1259 1260 // Default types with const payload 1261 nothrow @nogc unittest 1262 { 1263 alias Exp = Expected!(const(int)); 1264 static assert(is(typeof(Exp.init.value) == const(int))); 1265 auto res = Exp(42); 1266 assert(res); 1267 assert(res.hasValue && !res.hasError); 1268 assert(res.value == 42); 1269 static assert(!__traits(compiles, res.value = res.value)); 1270 } 1271 1272 // Default types with immutable payload 1273 unittest 1274 { 1275 alias Exp = Expected!(immutable(int)); 1276 static assert(is(typeof(Exp.init.value) == immutable(int))); 1277 auto res = Exp(42); 1278 assert(res); 1279 assert(res.hasValue && !res.hasError); 1280 assert(res.value == 42); 1281 static assert(!__traits(compiles, res.value = res.value)); 1282 } 1283 1284 // opAssign 1285 @system nothrow unittest 1286 { 1287 struct EnableDefaultConstructor { static immutable bool enableDefaultConstructor = true; } 1288 // value 1289 { 1290 auto res = Expected!(int, string, EnableDefaultConstructor).init; 1291 res = 42; 1292 assert(res); 1293 assert(res.hasValue && !res.hasError); 1294 assert(res.value == 42); 1295 res = 43; 1296 assertThrown!Throwable(res = "foo"); 1297 } 1298 1299 // error 1300 { 1301 auto res = Expected!(int, string, EnableDefaultConstructor)("42"); 1302 assert(!res.hasValue && res.hasError); 1303 assert(res.error == "42"); 1304 res = "foo"; 1305 assert(res.error == "foo"); 1306 assertThrown!Throwable(res = 42); 1307 } 1308 } 1309 1310 // Same types 1311 @system nothrow unittest 1312 { 1313 { 1314 alias Exp = Expected!(int, int); 1315 auto res = Exp(42); 1316 assert(res); 1317 assert(res.hasValue && !res.hasError); 1318 assert(res.value == 42); 1319 assertThrown!Throwable(res.error()); 1320 } 1321 1322 // const mix 1323 { 1324 alias Exp = Expected!(const(int), int); 1325 auto res = Exp(const int(42)); 1326 auto val = res.value; 1327 static assert(is(typeof(val) == const int)); 1328 assert(res); 1329 assert(res.hasValue && !res.hasError); 1330 assert(res.value == 42); 1331 assertThrown!Throwable(res.error); 1332 } 1333 1334 // const mix 1335 { 1336 alias Exp = Expected!(const(int), int); 1337 auto res = Exp(42); 1338 auto err = res.error; 1339 static assert(is(typeof(err) == int)); 1340 assert(!res); 1341 assert(!res.hasValue && res.hasError); 1342 assert(res.error == 42); 1343 assertThrown!Throwable(res.value); 1344 } 1345 1346 // immutable mix 1347 { 1348 alias Exp = Expected!(immutable(int), int); 1349 auto res = Exp(immutable int(42)); 1350 auto val = res.value; 1351 static assert(is(typeof(val) == immutable int)); 1352 assert(res); 1353 assert(res.hasValue && !res.hasError); 1354 assert(res.value == 42); 1355 assertThrown!Throwable(res.error); 1356 } 1357 1358 // immutable mix 1359 { 1360 alias Exp = Expected!(immutable(int), int); 1361 auto res = Exp(42); 1362 auto err = res.error; 1363 static assert(is(typeof(err) == int)); 1364 assert(!res); 1365 assert(!res.hasValue && res.hasError); 1366 assert(res.error == 42); 1367 assertThrown!Throwable(res.value); 1368 } 1369 1370 // immutable mix reverse 1371 { 1372 alias Exp = Expected!(int, immutable(int)); 1373 auto res = Exp(immutable int(42)); 1374 auto err = res.error; 1375 static assert(is(typeof(err) == immutable int)); 1376 assert(!res); 1377 assert(!res.hasValue && res.hasError); 1378 assert(res.error == 42); 1379 assertThrown!Throwable(res.value); 1380 } 1381 1382 // immutable mix reverse 1383 { 1384 alias Exp = Expected!(int, immutable(int)); 1385 auto res = Exp(42); 1386 auto val = res.value; 1387 static assert(is(typeof(val) == int)); 1388 assert(res); 1389 assert(res.hasValue && !res.hasError); 1390 assert(res.value == 42); 1391 assertThrown!Throwable(res.error); 1392 } 1393 } 1394 1395 // void payload 1396 nothrow @nogc unittest 1397 { 1398 alias Exp = Expected!(void, int); 1399 static assert (!__traits(hasMember, Exp, "hasValue")); 1400 static assert (!__traits(hasMember, Exp, "value")); 1401 1402 { 1403 auto res = Exp(); 1404 assert(res); 1405 assert(!res.hasError); 1406 } 1407 1408 { 1409 auto res = Exp(42); 1410 assert(!res); 1411 assert(res.hasError); 1412 assert(res.error == 42); 1413 } 1414 } 1415 1416 // opEquals 1417 unittest 1418 { 1419 assert(expected(42) == 42); 1420 assert(expected(42) != 43); 1421 assert(expected("foo") == "foo"); 1422 assert(expected("foo") != "bar"); 1423 assert(expected("foo") == cast(const string)"foo"); 1424 assert(expected("foo") == cast(immutable string)"foo"); 1425 assert(expected(42) == expected(42)); 1426 assert(expected(42) != expected(43)); 1427 assert(expected(42) != unexpected!int("42")); 1428 1429 static assert(!__traits(compiles, unexpected("foo") == "foo")); 1430 assert(unexpected(42) == unexpected(42)); 1431 assert(unexpected(42) != unexpected(43)); 1432 assert(unexpected("foo") == unexpected("foo")); 1433 assert(unexpected("foo") != unexpected("bar")); 1434 } 1435 1436 //FIXME: doesn't work - some older dmd error 1437 static if (__VERSION__ >= 2082) 1438 { 1439 // toHash 1440 unittest 1441 { 1442 assert(expected(42).hashOf == 42.hashOf); 1443 assert(expected(42).hashOf != 43.hashOf); 1444 assert(expected(42).hashOf == expected(42).hashOf); 1445 assert(expected(42).hashOf != expected(43).hashOf); 1446 assert(expected(42).hashOf == expected!bool(42).hashOf); 1447 assert(expected(42).hashOf != unexpected("foo").hashOf); 1448 assert(unexpected("foo").hashOf == unexpected("foo").hashOf); 1449 } 1450 } 1451 1452 // range interface 1453 unittest 1454 { 1455 { 1456 auto r = expected(42); 1457 assert(!r.empty); 1458 assert(r.front == 42); 1459 r.popFront(); 1460 assert(r.empty); 1461 } 1462 1463 { 1464 auto r = unexpected!int("foo"); 1465 assert(r.empty); 1466 } 1467 1468 { 1469 auto r = unexpected("foo"); 1470 static assert(!__traits(compiles, r.empty)); 1471 static assert(!__traits(compiles, r.front)); 1472 static assert(!__traits(compiles, r.popFront)); 1473 } 1474 }