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 features 14 15 $(LIST 16 * lightweight, no other external dependencies 17 * works with `pure`, `@safe`, `@nogc`, `nothrow`, and `immutable` 18 * provides methods: `ok`, `err`, `consume`, `expect`, `expectErr`, `andThen`, `orElse`, `map`, `mapError`, `mapOrElse` 19 * type inference for ease of use with `ok` and `err` 20 * allows to use same types for `T` and `E` 21 * allows to define $(LREF Expected) without value (`void` for `T`) - can be disabled with custom `Hook` 22 * provides facility to change the $(LREF Expected) behavior by custom `Hook` implementation using the Design by introspection paradigm. 23 * can enforce result check (with a cost) 24 * can behave like a normal `Exception` handled code by changing the used `Hook` implementation 25 * range interface 26 ) 27 28 ## Description 29 30 Actual $(LREF Expected) type is defined as $(D Expected!(T, E, Hook)), where: 31 32 $(LIST 33 * `T` defines type of the success value 34 * `E` defines type of the error 35 * `Hook` defines behavior of the $(LREF Expected) 36 ) 37 38 Default type for error is `string`, i.e. `Expected!int` is the same as `Expected!(int, string)`. 39 40 $(LREF Abort) is used as a default hook. 41 42 ### Hooks 43 44 $(LREF Expected) has customizable behavior with the help of a third type parameter, 45 `Hook`. Depending on what methods `Hook` defines, core operations on the 46 $(LREF Expected) may be verified or completely redefined. 47 If `Hook` defines no method at all and carries no state, there is no change in 48 default behavior. 49 50 This module provides a few predefined hooks (below) that add useful behavior to 51 $(LREF Expected): 52 53 $(BOOKTABLE , 54 $(TR $(TD $(LREF Abort)) $(TD 55 Fails every incorrect operation with a call to `assert(0)`. 56 It is the default third parameter, i.e. $(D Expected!short) is the same as 57 $(D Expected!(short, string, Abort)). 58 )) 59 $(TR $(TD $(LREF Throw)) $(TD 60 Fails every incorrect operation by throwing an exception. 61 )) 62 $(TR $(TD $(LREF AsException)) $(TD 63 With this hook implementation $(LREF Expected) behaves just like regular 64 $(D Exception) handled code. 65 66 That means when function returns $(LREF expected) value, it returns instance 67 of $(LREF Expected) with a success value. 68 But when it tries to return error, $(D Exception) is thrown right away, 69 i.e. $(LREF Expected) fails in constructor. 70 )) 71 $(TR $(TD $(LREF RCAbort)) $(TD 72 Similar to $(LREF Abort) hook but uses reference counted payload instead 73 which enables checking if the caller properly checked result of the 74 $(LREF Expected). 75 )) 76 ) 77 78 The hook's members are looked up statically in a Design by Introspection manner 79 and are all optional. The table below illustrates the members that a hook type 80 may define and their influence over the behavior of the `Checked` type using it. 81 In the table, `hook` is an alias for `Hook` if the type `Hook` does not 82 introduce any state, or an object of type `Hook` otherwise. 83 84 $(TABLE_ROWS 85 * + Hook member 86 + Semantics in Expected!(T, E, Hook) 87 * - `enableDefaultConstructor` 88 - If defined, $(LREF Expected) would have enabled or disabled default constructor 89 based on it's `bool` value. Default constructor is disabled by default. 90 `opAssign` for value and error types is generated if default constructor is enabled. 91 * - `enableCopyConstructor` 92 - If defined, $(LREF Expected) would have enabled or disabled copy constructor based 93 on it's `bool` value. It is enabled by default. When disabled, it enables automatic 94 check if the result was checked either for value or error. 95 When not checked it calls $(D hook.onUnchecked) if provided. 96 97 $(NOTE WARNING: As currently it's not possible to change internal state of `const` 98 or `immutable` object, automatic checking would't work on these. Hopefully with 99 `__mutable` proposal..) 100 * - `enableRefCountedPayload` 101 - Set $(LREF Expected) instances to use reference counted payload storage. It's usefull 102 when combined with `onUnchecked` to forcibly check that the result was checked for value 103 or error. 104 * - `enableVoidValue` 105 - Defines if $(LREF Expected) supports `void` values. It's enabled by default so this 106 hook can be used to disable it. 107 * - `onAccessEmptyValue` 108 - If value is accessed on unitialized $(LREF Expected) or $(LREF Expected) with error 109 value, $(D hook.onAccessEmptyValue!E(err)) is called. If hook doesn't implement the 110 handler, `T.init` is returned. 111 * - `onAccessEmptyError` 112 - If error is accessed on unitialized $(LREF Expected) or $(LREF Expected) with value, 113 $(D hook.onAccessEmptyError()) is called. If hook doesn't implement the handler, 114 `E.init` is returned. 115 * - `onUnchecked` 116 - If the result of $(LREF Expected) isn't checked, $(D hook.onUnchecked()) is called to 117 handle the error. If hook doesn't implement the handler, assert is thrown. 118 $(NOTE Note that `hook.enableCopyConstructor` must be `false` or `hook.enableRefCountedPayload` 119 must be `true` for checks to work.) 120 * - `onValueSet` 121 - $(D hook.onValueSet!T(val)) function is called when success value is being set to 122 $(LREF Expected). It can be used for loging purposes, etc. 123 * - `onErrorSet` 124 - $(D hook.onErrorSet!E(err)) function is called when error value is being set to 125 $(LREF Expected). This hook function is used by $(LREF AsException) hook implementation 126 to change `Expected` idiom to normal `Exception` handling behavior. 127 ) 128 129 License: BSL-1.0 130 Author: Tomáš Chaloupka 131 +/ 132 133 //TODO: collect errno function call - see https://dlang.org/phobos/std_exception.html#ErrnoException 134 135 module expected; 136 137 /// $(H3 Basic usage) 138 @("Basic usage example") 139 @safe unittest 140 { 141 auto foo(int i) { 142 if (i == 0) return err!int("oops"); 143 return ok(42 / i); 144 } 145 146 version (D_Exceptions) 147 { 148 auto bar(int i) { 149 if (i == 0) throw new Exception("err"); 150 return i-1; 151 } 152 } 153 154 // basic checks 155 assert(foo(2)); 156 assert(foo(2).hasValue); 157 assert(!foo(2).hasError); 158 assert(foo(2).value == 21); 159 160 assert(!foo(0)); 161 assert(!foo(0).hasValue); 162 assert(foo(0).hasError); 163 assert(foo(0).error == "oops"); 164 165 // void result 166 assert(ok()); // no error -> success 167 assert(!ok().hasError); 168 // assert(err("foo").hasValue); // doesn't have hasValue and value properties 169 170 version (D_Exceptions) 171 { 172 // expected from throwing function 173 assert(consume!bar(1) == 0); 174 assert(consume!bar(0).error.msg == "err"); 175 } 176 177 // orElse 178 assert(foo(2).orElse!(() => 0) == 21); 179 assert(foo(0).orElse(100) == 100); 180 181 // andThen 182 assert(foo(2).andThen(foo(6)) == 7); 183 assert(foo(0).andThen(foo(6)).error == "oops"); 184 185 // map 186 assert(foo(2).map!(a => a*2).map!(a => a - 2) == 40); 187 assert(foo(0).map!(a => a*2).map!(a => a - 2).error == "oops"); 188 189 // mapError 190 assert(foo(0).mapError!(e => "OOPS").error == "OOPS"); 191 assert(foo(2).mapError!(e => "OOPS") == 21); 192 193 // mapOrElse 194 assert(foo(2).mapOrElse!(v => v*2, e => 0) == 42); 195 assert(foo(0).mapOrElse!(v => v*2, e => 0) == 0); 196 } 197 198 version (D_Exceptions) 199 { 200 /// $(H3 Advanced usage - behavior modification) 201 @("Advanced usage example") 202 @safe unittest 203 { 204 import exp = expected; 205 206 // define our Expected type using Exception as Error values 207 // and Throw hook, which throws when empty value or error is accessed 208 template Expected(T) 209 { 210 alias Expected = exp.Expected!(T, Exception, Throw); 211 } 212 213 // create wrappers for simplified usage of our Expected 214 auto ok(T)(T val) { return exp.ok!(Exception, Throw)(val); } 215 auto err(T)(Exception err) { return exp.err!(T, Throw)(err); } 216 217 // use it as normal 218 assert(ok(42) == 42); 219 assert(err!int(new Exception("foo")).orElse(0) == 0); 220 assertThrown(ok(42).error); 221 assertThrown(err!int(new Exception("bar")).value); 222 } 223 } 224 225 version (unittest) { 226 import std.algorithm : reverse; 227 import std.exception : assertThrown, collectExceptionMsg; 228 } 229 230 /++ 231 `Expected!(T, E)` is a type that represents either success or failure. 232 233 Type `T` is used for success value. 234 If `T` is `void`, then $(LREF Expected) can only hold error value and is considered a success when there is no error value. 235 236 Type `E` is used for error value. 237 The default type for the error value is `string`. 238 239 Default behavior of $(LREF Expected) can be modified by the `Hook` template parameter. 240 241 Params: 242 T = represents type of the expected value 243 E = represents type of the error value. 244 Hook = defines the $(LREF Expected) type behavior 245 +/ 246 struct Expected(T, E = string, Hook = Abort) 247 if (!is(E == void) && (isVoidValueEnabled!Hook || !is(T == void))) 248 { 249 import core.lifetime : forward; 250 import std.meta : AliasSeq, Filter, NoDuplicates; 251 import std.traits: isAssignable, isCopyable, hasIndirections, Unqual; 252 253 private template noVoid(T) { enum noVoid = !is(T == void); } // Erase removes qualifiers 254 private alias Types = NoDuplicates!(Filter!(noVoid, AliasSeq!(T, E))); 255 256 static foreach (i, CT; Types) 257 { 258 /++ 259 Constructs an $(LREF Expected) with value or error based on the tye of the provided. 260 261 In case when `T == E`, it constructs $(LREF Expected) with value. 262 263 In case when `T == void`, it constructs $(LREF Expected) with error value. 264 265 Default constructor (if enabled) initializes $(LREF Expected) to `T.init` value. 266 If `T == void`, it initializes $(LREF Expected) with no error. 267 +/ 268 this()(auto ref CT val) 269 { 270 static if (isRefCountedPayloadEnabled!Hook) 271 { 272 initialize(forward!val); 273 } 274 else 275 { 276 storage = Payload(forward!val); 277 } 278 setState!CT(); 279 280 static if (hasOnValueSet!(Hook, CT)) { if (state == State.value) __traits(getMember, Hook, "onValueSet")(getValue()); } 281 static if (hasOnErrorSet!(Hook, CT)) { if (state == State.error) __traits(getMember, Hook, "onErrorSet")(getError()); } 282 } 283 284 // static if (isCopyable!CT) 285 // { 286 // / ditto 287 // this()(auto ref const(CT) val) const 288 // { 289 // storage = const(Payload)(val); 290 // setState!CT(); 291 292 // static if (hasOnValueSet!(Hook, CT)) { if (state == State.value) __traits(getMember, Hook, "onValueSet")(val); } 293 // static if (hasOnErrorSet!(Hook, CT)) { if (state == State.error) __traits(getMember, Hook, "onErrorSet")(val); } 294 // } 295 296 // /// ditto 297 // this()(auto ref immutable(CT) val) immutable 298 // { 299 // storage = immutable(Payload)(val); 300 // setState!CT(); 301 302 // static if (hasOnValueSet!(Hook, CT)) { if (state == State.value) __traits(getMember, Hook, "onValueSet")(val); } 303 // static if (hasOnErrorSet!(Hook, CT)) { if (state == State.error) __traits(getMember, Hook, "onErrorSet")(val); } 304 // } 305 // } 306 // else 307 // { 308 // @disable this(const(CT) val) const; 309 // @disable this(immutable(CT) val) immutable; 310 // } 311 } 312 313 // generate constructor with flag to determine type of value 314 static if (Types.length == 1 && !is(T == void)) 315 { 316 /++ Constructs an $(LREF Expected) with value or error based on the provided flag. 317 This constructor is available only for cases when value and error has the same type, 318 so we can still construct $(LREF Expected) with value or error. 319 320 Params: 321 val = Value to set as value or error 322 success = If `true`, $(LREF Expected) with value is created, $(LREF Expected) with error otherwise. 323 +/ 324 this()(auto ref E val, bool success) 325 { 326 static if (isRefCountedPayloadEnabled!Hook) 327 { 328 initialize(forward!val); 329 } 330 else 331 { 332 storage = Payload(forward!val); 333 } 334 setState!E(success ? State.value : State.error); 335 336 static if (hasOnValueSet!(Hook, E)) { if (state == State.value) __traits(getMember, Hook, "onValueSet")(getValue()); } 337 static if (hasOnErrorSet!(Hook, E)) { if (state == State.error) __traits(getMember, Hook, "onErrorSet")(getError()); } 338 } 339 340 // static if (isCopyable!E) 341 // { 342 // /// ditto 343 // this()(auto ref const(E) val, bool success) const 344 // { 345 // storage = const(Payload)(val); 346 // setState!E(success ? State.value : State.error); 347 348 // static if (hasOnValueSet!(Hook, E)) { if (state == State.value) __traits(getMember, Hook, "onValueSet")(val); } 349 // static if (hasOnErrorSet!(Hook, E)) { if (state == State.error) __traits(getMember, Hook, "onErrorSet")(val); } 350 // } 351 352 // /// ditto 353 // this()(auto ref immutable(E) val, bool success) immutable 354 // { 355 // storage = immutable(Payload)(val); 356 // setState!E(success ? State.value : State.error); 357 358 // static if (hasOnValueSet!(Hook, E)) { if (state == State.value) __traits(getMember, Hook, "onValueSet")(val); } 359 // static if (hasOnErrorSet!(Hook, E)) { if (state == State.error) __traits(getMember, Hook, "onErrorSet")(val); } 360 // } 361 // } 362 // else 363 // { 364 // @disable this(const(E) val, bool success) const; 365 // @disable this(immutable(E) val, bool success) immutable; 366 // } 367 } 368 369 static if (!is(T == void) && !isDefaultConstructorEnabled!Hook) @disable this(); 370 371 static if (!isCopyConstructorEnabled!Hook) @disable this(this); 372 373 static if (isChecked!Hook || isRefCountedPayloadEnabled!Hook) 374 { 375 ~this() 376 { 377 static void onUnchecked() 378 { 379 static if (hasOnUnchecked!Hook) __traits(getMember, Hook, "onUnchecked")(); 380 else assert(0, "unchecked result"); 381 } 382 383 static if (isRefCountedPayloadEnabled!Hook) 384 { 385 if (!storage) return; 386 assert(storage.count > 0); 387 388 if (--storage.count) return; 389 390 // Done, deallocate 391 static if (isChecked!Hook) bool ch = checked; 392 destroy(storage.payload); 393 static if (enableGCScan) () @trusted { pureGcRemoveRange(&storage.payload); } (); 394 395 () @trusted 396 { 397 pureFree(storage); 398 storage = null; 399 }(); 400 401 static if (isChecked!Hook) { if (!ch) onUnchecked(); } 402 } 403 else static if (isChecked!Hook) { if (!checked) onUnchecked(); } 404 } 405 } 406 407 static if (isDefaultConstructorEnabled!Hook) 408 { 409 static foreach (i, CT; Types) 410 { 411 static if (isAssignable!CT) 412 { 413 /++ Assigns a value or error to an $(LREF Expected). 414 415 Note: This is only allowed when default constructor is also enabled. 416 +/ 417 void opAssign()(auto ref CT rhs) 418 { 419 setState!CT(); // check state asserts before change 420 auto s = state; 421 static if (isRefCountedPayloadEnabled!Hook) 422 { 423 if (!storage) initialize(forward!rhs); 424 else storage.payload = Payload(forward!rhs); 425 } 426 else storage = Payload(forward!rhs); 427 setState!(CT)(s); // set previous state 428 429 static if (hasOnValueSet!(Hook, CT)) { if (state == State.value) __traits(getMember, Hook, "onValueSet")(getValue()); } 430 static if (hasOnErrorSet!(Hook, CT)) { if (state == State.error) __traits(getMember, Hook, "onErrorSet")(getError()); } 431 } 432 } 433 } 434 } 435 436 //damn these are ugly :( 437 static if (!isChecked!Hook) { 438 /++ Implicit conversion to bool. 439 Returns: `true` if there is no error set, `false` otherwise. 440 +/ 441 bool opCast(T)() const if (is(T == bool)) { return !this.hasError; } 442 } else { 443 /// ditto 444 bool opCast(T)() if (is(T == bool)) { return !this.hasError; } 445 } 446 447 static if (!is(T == void)) 448 { 449 static if (!isChecked!Hook) { 450 /++ Checks whether this $(LREF Expected) object contains a specific expected value. 451 452 * `opEquals` for the value is available only when `T != void`. 453 * `opEquals` for the error isn't available, use equality test for $(LREF Expected) in that case. 454 +/ 455 bool opEquals()(const auto ref T rhs) const 456 { 457 return hasValue && value == rhs; 458 } 459 } else { 460 /// ditto 461 bool opEquals()(auto ref T rhs) { return hasValue && value == forward!rhs; } 462 } 463 } 464 465 static if (!isChecked!Hook) { 466 /// Checks whether this $(LREF Expected) object and `rhs` contain the same expected value or error value. 467 bool opEquals()(const auto ref Expected!(T, E, Hook) rhs) const 468 { 469 if (state != rhs.state) return false; 470 static if (!is(T == void)) { if (hasValue) return value == rhs.value; } 471 return error == rhs.error; 472 } 473 } else { 474 /// ditto 475 bool opEquals()(auto ref Expected!(T, E, Hook) rhs) 476 { 477 if (state != rhs.state) return false; 478 static if (!is(T == void)) { if (hasValue) return value == forwardValue!rhs; } 479 return error == forwardError!rhs; 480 } 481 } 482 483 static if (!isChecked!Hook) { 484 /++ Calculates the hash value of the $(LREF Expected) in a way that iff it has a value, 485 it returns hash of the value. 486 Hash is computed using internal state and storage of the $(LREF Expected) otherwise. 487 +/ 488 size_t toHash()() const nothrow 489 { 490 static if (!is(T == void)) { if (hasValue) return value.hashOf; } 491 return storage.hashOf(state); 492 } 493 } else { 494 /// ditto 495 size_t toHash()() nothrow 496 { 497 static if (!is(T == void)) { if (hasValue) return value.hashOf; } 498 return storage.hashOf(state); 499 } 500 } 501 502 static if (!is(T == void)) 503 { 504 static if (!isChecked!Hook) { 505 /// Checks if $(LREF Expected) has value 506 @property bool hasValue()() const { return state == State.value; } 507 } 508 else { 509 /// ditto 510 @property bool hasValue()() 511 { 512 checked = true; 513 return state == State.value; 514 } 515 } 516 517 static if (!isChecked!Hook) { 518 /++ 519 Returns the expected value if there is one. 520 521 With default `Abort` hook, it asserts when there is no value. 522 It calls hook's `onAccessEmptyValue` otherwise. 523 524 It returns `T.init` when hook doesn't provide `onAccessEmptyValue`. 525 +/ 526 @property auto ref inout(T) value() inout 527 { 528 if (state != State.value) 529 { 530 static if (hasOnAccessEmptyValue!(Hook, E)) 531 __traits(getMember, Hook, "onAccessEmptyValue")(state == State.error ? getError() : E.init); 532 else return T.init; 533 } 534 return getValue(); 535 } 536 } else { 537 @property auto ref T value() 538 { 539 checked = true; 540 541 if (state != State.value) 542 { 543 static if (hasOnAccessEmptyValue!(Hook, E)) 544 __traits(getMember, Hook, "onAccessEmptyValue")(state == State.error ? getError() : E.init); 545 else return T.init; 546 } 547 return getValue(); 548 } 549 } 550 } 551 552 static if (!isChecked!Hook) { 553 /// Checks if $(LREF Expected) has error 554 @property bool hasError()() const { return state == State.error; } 555 } else { 556 /// ditto 557 @property bool hasError()() 558 { 559 checked = true; 560 return state == State.error; 561 } 562 } 563 564 static if (!isChecked!Hook) { 565 /++ 566 Returns the error value. May only be called when `hasValue` returns `false`. 567 568 If there is no error value, it calls hook's `onAccessEmptyError`. 569 570 It returns `E.init` when hook doesn't provide `onAccessEmptyError`. 571 +/ 572 @property auto ref inout(E) error() inout 573 { 574 if (state != State.error) 575 { 576 static if (hasOnAccessEmptyError!Hook) __traits(getMember, Hook, "onAccessEmptyError")(); 577 else return E.init; 578 } 579 return getError; 580 } 581 } else { 582 @property auto ref E error() 583 { 584 checked = true; 585 586 if (state != State.error) 587 { 588 static if (hasOnAccessEmptyError!Hook) __traits(getMember, Hook, "onAccessEmptyError")(); 589 else return E.init; 590 } 591 return getError; 592 } 593 } 594 595 // range interface 596 static if (!is(T == void)) 597 { 598 static if (!isChecked!Hook) { 599 /++ Range interface defined by `empty`, `front`, `popFront`. 600 Yields one value if $(LREF Expected) has value. 601 602 If `T == void`, range interface isn't defined. 603 +/ 604 @property bool empty() const { return state != State.value; } 605 606 /// ditto 607 @property auto ref inout(T) front() inout { return value; } 608 } else { 609 @property bool empty() { checked = true; return state != State.value; } 610 611 /// ditto 612 @property auto ref T front() { return value; } 613 } 614 615 /// ditto 616 void popFront() { state = State.empty; } 617 } 618 619 private: 620 621 //FIXME: can probably be union instead, but that doesn't work well with destructors and copy constructors/postblits 622 //and need to be handled manually - so for now we use a safer variant 623 struct Payload 624 { 625 Types values; 626 627 // generate payload constructors 628 static foreach (i, CT; Types) 629 { 630 this()(auto ref CT val) 631 { 632 __traits(getMember, Payload, "values")[i] = forward!val; 633 } 634 635 // static if (isCopyable!CT) 636 // { 637 // this()(auto ref const(CT) val) const { __traits(getMember, Payload, "values")[i] = val; } 638 // this()(auto ref immutable(CT) val) immutable { __traits(getMember, Payload, "values")[i] = val; } 639 // } 640 // else 641 // { 642 // @disable this(const(CT) val) const; 643 // @disable this(immutable(CT) val) immutable; 644 // } 645 } 646 } 647 648 static if (isRefCountedPayloadEnabled!Hook) 649 { 650 version (D_BetterC) enum enableGCScan = false; 651 else enum enableGCScan = hasIndirections!Payload; 652 653 // purify memory management functions - see https://github.com/dlang/phobos/pull/4832 654 extern(C) pure nothrow @nogc static 655 { 656 pragma(mangle, "malloc") void* pureMalloc(size_t); 657 pragma(mangle, "free") void pureFree( void *ptr ); 658 static if (enableGCScan) 659 { 660 pragma(mangle, "calloc") void* pureCalloc(size_t nmemb, size_t size); 661 pragma(mangle, "gc_addRange") void pureGcAddRange( in void* p, size_t sz, const TypeInfo ti = null ); 662 pragma(mangle, "gc_removeRange") void pureGcRemoveRange( in void* p ); 663 } 664 } 665 666 struct Impl 667 { 668 Payload payload; 669 State state; 670 size_t count; 671 static if (isChecked!Hook) bool checked; 672 } 673 674 void initialize(A...)(auto ref A args) 675 { 676 import std.conv : emplace; 677 678 allocateStore(); 679 emplace(&storage.payload, forward!args); 680 storage.count = 1; 681 storage.state = State.empty; 682 static if (isChecked!Hook) storage.checked = false; 683 } 684 685 void allocateStore() nothrow pure @trusted 686 { 687 assert(!storage); 688 static if (enableGCScan) 689 { 690 storage = cast(Impl*) pureCalloc(1, Impl.sizeof); 691 if (!storage) assert(0, "Memory allocation failed"); 692 pureGcAddRange(&storage.payload, Payload.sizeof); 693 } 694 else 695 { 696 storage = cast(Impl*) pureMalloc(Impl.sizeof); 697 if (!storage) assert(0, "Memory allocation failed"); 698 } 699 } 700 701 Impl* storage; 702 703 @property nothrow @safe pure @nogc 704 size_t refCount() const { return storage !is null ? storage.count : 0; } 705 706 @property nothrow @safe pure @nogc 707 State state() const { return storage !is null ? storage.state : State.empty; } 708 709 @property nothrow @safe pure @nogc 710 void state(State state) { assert(storage); storage.state = state; } 711 712 static if (isChecked!Hook) 713 { 714 @property nothrow @safe pure @nogc 715 bool checked() const { assert(storage); return storage.checked; } 716 717 @property nothrow @safe pure @nogc 718 void checked(bool ch) { assert(storage); storage.checked = ch; } 719 } 720 721 ref inout(E) getError()() inout 722 { 723 assert(storage); 724 static if (__VERSION__ < 2078) // workaround - see: https://issues.dlang.org/show_bug.cgi?id=15094 725 { 726 auto p = &storage.payload; 727 static if (Types.length == 1) return __traits(getMember, p, "values")[0]; 728 else return __traits(getMember, p, "values")[1]; 729 } 730 else 731 { 732 static if (Types.length == 1) return __traits(getMember, storage.payload, "values")[0]; 733 else return __traits(getMember, storage.payload, "values")[1]; 734 } 735 } 736 737 static if (!is(T == void)) 738 { 739 ref inout(T) getValue()() inout 740 { 741 assert(storage); 742 static if (__VERSION__ < 2078) // workaround - see: https://issues.dlang.org/show_bug.cgi?id=15094 743 { 744 auto p = &storage.payload; 745 return __traits(getMember, p, "values")[0]; 746 } 747 else return __traits(getMember, storage.payload, "values")[0]; 748 } 749 } 750 751 this(this) @safe pure nothrow @nogc 752 { 753 if (!storage) return; 754 ++storage.count; 755 } 756 } 757 else 758 { 759 Payload storage; 760 State state = State.empty; 761 static if (isChecked!Hook) bool checked = false; 762 763 ref inout(E) getError()() inout 764 { 765 static if (Types.length == 1) return __traits(getMember, storage, "values")[0]; 766 else return __traits(getMember, storage, "values")[1]; 767 } 768 769 static if (!is(T == void)) 770 { 771 ref inout(T) getValue()() inout 772 { 773 return __traits(getMember, storage, "values")[0]; 774 } 775 } 776 } 777 778 enum State : ubyte { empty, value, error } 779 780 void setState(MT)(State known = State.empty) 781 { 782 State s; 783 if (known != State.empty) s = known; 784 else 785 { 786 static if (Types.length == 1 && is(T == void)) s = State.error; 787 else static if (Types.length == 1 || is(MT == T)) s = State.value; 788 else s = State.error; 789 } 790 791 //TODO: change with Hook? 792 assert(state == State.empty || state == s, "Can't change meaning of already set Expected type"); 793 state = s; 794 } 795 } 796 797 /++ Template to determine if hook enables or disables copy constructor. 798 799 It is enabled by default. 800 801 See $(LREF hasOnUnchecked) handler, which can be used in combination with disabled 802 copy constructor to enforce that the result is checked. 803 804 $(WARNING If copy constructor is disabled, it severely limits function chaining 805 as $(LREF Expected) needs to be passed as rvalue in that case.) 806 +/ 807 template isCopyConstructorEnabled(Hook) 808 { 809 static if (__traits(hasMember, Hook, "enableCopyConstructor")) 810 { 811 static assert( 812 is(typeof(__traits(getMember, Hook, "enableCopyConstructor")) : bool), 813 "Hook's enableCopyConstructor is expected to be of type bool" 814 ); 815 enum isCopyConstructorEnabled = __traits(getMember, Hook, "enableCopyConstructor"); 816 } 817 else enum isCopyConstructorEnabled = true; 818 } 819 820 /// 821 @("isCopyConstructorEnabled") 822 @safe unittest 823 { 824 struct Foo {} 825 struct Bar { static immutable bool enableCopyConstructor = false; } 826 static assert(isCopyConstructorEnabled!Foo); 827 static assert(!isCopyConstructorEnabled!Bar); 828 } 829 830 /++ Template to determine if hook defines that the $(LREF Expected) storage should 831 use refcounted state storage. 832 833 If this is enabled, payload is mallocated on the heap and dealocated with the 834 destruction of last $(Expected) instance. 835 836 See $(LREF hasOnUnchecked) handler, which can be used in combination with refcounted 837 payload to enforce that the result is checked. 838 +/ 839 template isRefCountedPayloadEnabled(Hook) 840 { 841 static if (__traits(hasMember, Hook, "enableRefCountedPayload")) 842 { 843 static assert( 844 is(typeof(__traits(getMember, Hook, "enableRefCountedPayload")) : bool), 845 "Hook's enableCopyConstructor is expected to be of type bool" 846 ); 847 enum isRefCountedPayloadEnabled = __traits(getMember, Hook, "enableRefCountedPayload"); 848 static assert ( 849 !isRefCountedPayloadEnabled || isCopyConstructorEnabled!Hook, 850 "Refcounted payload wouldn't work without copy constructor enabled" 851 ); 852 } 853 else enum isRefCountedPayloadEnabled = false; 854 } 855 856 /// 857 @("isRefCountedPayloadEnabled") 858 @safe unittest 859 { 860 struct Foo {} 861 struct Bar { 862 static immutable bool enableCopyConstructor = false; 863 static immutable bool enableRefCountedPayload = true; 864 } 865 struct Hook { static immutable bool enableRefCountedPayload = true; } 866 static assert(!isRefCountedPayloadEnabled!Foo); 867 static assert(!__traits(compiles, isRefCountedPayloadEnabled!Bar)); 868 static assert(isRefCountedPayloadEnabled!Hook); 869 } 870 871 // just a helper to determine check behavior 872 private template isChecked(Hook) 873 { 874 enum isChecked = !isCopyConstructorEnabled!Hook || isRefCountedPayloadEnabled!Hook; 875 } 876 877 /// Template to determine if provided Hook enables default constructor for $(LREF Expected) 878 template isDefaultConstructorEnabled(Hook) 879 { 880 static if (__traits(hasMember, Hook, "enableDefaultConstructor")) 881 { 882 static assert( 883 is(typeof(__traits(getMember, Hook, "enableDefaultConstructor")) : bool), 884 "Hook's enableDefaultConstructor is expected to be of type bool" 885 ); 886 enum isDefaultConstructorEnabled = __traits(getMember, Hook, "enableDefaultConstructor"); 887 } 888 else enum isDefaultConstructorEnabled = false; 889 } 890 891 /// 892 @("isDefaultConstructorEnabled") 893 @safe unittest 894 { 895 struct Foo {} 896 struct Bar { static immutable bool enableDefaultConstructor = true; } 897 static assert(!isDefaultConstructorEnabled!Foo); 898 static assert(isDefaultConstructorEnabled!Bar); 899 } 900 901 /// Template to determine if provided Hook enables void values for $(LREF Expected) 902 template isVoidValueEnabled(Hook) 903 { 904 static if (__traits(hasMember, Hook, "enableVoidValue")) 905 { 906 static assert( 907 is(typeof(__traits(getMember, Hook, "enableVoidValue")) : bool), 908 "Hook's enableVoidValue is expected to be of type bool" 909 ); 910 enum isVoidValueEnabled = __traits(getMember, Hook, "isVoidValueEnabled"); 911 } 912 else enum isVoidValueEnabled = true; 913 } 914 915 /// 916 @("isVoidValueEnabled") 917 @safe unittest 918 { 919 struct Hook { static immutable bool enableVoidValue = false; } 920 assert(!ok().hasError); // void values are enabled by default 921 static assert(!__traits(compiles, ok!(string, Hook)())); // won't compile 922 } 923 924 /// Template to determine if hook provides function called on empty value. 925 template hasOnAccessEmptyValue(Hook, E) 926 { 927 static if (__traits(hasMember, Hook, "onAccessEmptyValue")) 928 { 929 static assert( 930 is(typeof(__traits(getMember, Hook, "onAccessEmptyValue")(E.init))), 931 "Hook's onAccessEmptyValue is expected to be callable with error value type" 932 ); 933 enum hasOnAccessEmptyValue = true; 934 } 935 else enum hasOnAccessEmptyValue = false; 936 } 937 938 /// 939 @("hasOnAccessEmptyValue") 940 @safe unittest 941 { 942 struct Foo {} 943 struct Bar { static void onAccessEmptyValue(E)(E err) {} } 944 static assert(!hasOnAccessEmptyValue!(Foo, string)); 945 static assert(hasOnAccessEmptyValue!(Bar, string)); 946 } 947 948 /++ Template to determine if hook provides function called on empty error. 949 +/ 950 template hasOnAccessEmptyError(Hook) 951 { 952 static if (__traits(hasMember, Hook, "onAccessEmptyError")) 953 { 954 static assert( 955 is(typeof(__traits(getMember, Hook, "onAccessEmptyError")())), 956 "Hook's onAccessEmptyValue is expected to be callable with no arguments" 957 ); 958 enum hasOnAccessEmptyError = true; 959 } 960 else enum hasOnAccessEmptyError = false; 961 } 962 963 /// 964 @("hasOnAccessEmptyError") 965 @safe unittest 966 { 967 struct Foo {} 968 struct Bar { static void onAccessEmptyError() {} } 969 static assert(!hasOnAccessEmptyError!Foo); 970 static assert(hasOnAccessEmptyError!Bar); 971 } 972 973 /++ Template to determine if hook provides custom handler for case 974 when the $(LREF Expected) result is not checked. 975 976 For this to work it currently also has to pass $(LREF isCopyConstructorEnabled) 977 as this is implemented by simple flag controled on $(LREF Expected) destructor. 978 +/ 979 template hasOnUnchecked(Hook) 980 { 981 static if (__traits(hasMember, Hook, "onUnchecked")) 982 { 983 static assert( 984 is(typeof(__traits(getMember, Hook, "onUnchecked")())), 985 "Hook's onUnchecked is expected to be callable with no arguments" 986 ); 987 static assert( 988 !isCopyConstructorEnabled!Hook || isRefCountedPayloadEnabled!Hook, 989 "For unchecked check to work, it is needed to also have disabled copy constructor or enabled reference counted payload" 990 ); 991 enum hasOnUnchecked = true; 992 } 993 else enum hasOnUnchecked = false; 994 } 995 996 version (D_Exceptions) 997 { 998 /// 999 @("hasOnUnchecked") 1000 @safe unittest 1001 { 1002 struct Foo {} 1003 struct Bar { static void onUnchecked() { } } 1004 struct Hook { 1005 static immutable bool enableCopyConstructor = false; 1006 static void onUnchecked() @safe { throw new Exception("result unchecked"); } 1007 } 1008 1009 // template checks 1010 static assert(!hasOnUnchecked!Foo); 1011 static assert(!__traits(compiles, hasOnUnchecked!Bar)); // missing disabled constructor 1012 static assert(hasOnUnchecked!Hook); 1013 1014 // copy constructor 1015 auto exp = ok!(string, Hook)(42); 1016 auto exp2 = err!(int, Hook)("foo"); 1017 static assert(!__traits(compiles, exp.andThen(ok!(string, Hook)(42)))); // disabled cc 1018 assert(exp.andThen(exp2).error == "foo"); // passed by ref so no this(this) called 1019 1020 // check for checked result 1021 assertThrown({ ok!(string, Hook)(42); }()); 1022 assertThrown({ err!(void, Hook)("foo"); }()); 1023 } 1024 } 1025 1026 /++ Template to determine if hook provides function called when value is set. 1027 +/ 1028 template hasOnValueSet(Hook, T) 1029 { 1030 static if (__traits(hasMember, Hook, "onValueSet")) 1031 { 1032 static assert( 1033 is(typeof(__traits(getMember, Hook, "onValueSet")(T.init))), 1034 "Hook's onValueSet is expected to be callable with value argument" 1035 ); 1036 enum hasOnValueSet = true; 1037 } 1038 else enum hasOnValueSet = false; 1039 } 1040 1041 /// 1042 @("hasOnValueSet") 1043 @safe unittest 1044 { 1045 struct Hook { 1046 static int lastValue; 1047 static void onValueSet(T)(auto ref T val) { lastValue = val; } 1048 } 1049 1050 static assert(hasOnValueSet!(Hook, int)); 1051 auto res = ok!(string, Hook)(42); 1052 assert(res.hasValue); 1053 assert(Hook.lastValue == 42); 1054 } 1055 1056 /++ Template to determine if hook provides function called when error is set. 1057 +/ 1058 template hasOnErrorSet(Hook, T) 1059 { 1060 static if (__traits(hasMember, Hook, "onErrorSet")) 1061 { 1062 static assert( 1063 is(typeof(__traits(getMember, Hook, "onErrorSet")(T.init))), 1064 "Hook's onErrorSet is expected to be callable with error argument" 1065 ); 1066 enum hasOnErrorSet = true; 1067 } 1068 else enum hasOnErrorSet = false; 1069 } 1070 1071 /// 1072 @("hasOnErrorSet") 1073 @safe unittest 1074 { 1075 struct Hook { 1076 static string lastErr; 1077 static void onErrorSet(E)(auto ref E err) { lastErr = err; } 1078 } 1079 1080 static assert(hasOnErrorSet!(Hook, string)); 1081 auto res = err!(int, Hook)("foo"); 1082 assert(res.hasError); 1083 assert(Hook.lastErr == "foo"); 1084 } 1085 1086 /++ Default hook implementation for $(LREF Expected) 1087 +/ 1088 struct Abort 1089 { 1090 static: 1091 /++ Default constructor for $(LREF Expected) is disabled. 1092 Same with the `opAssign`, so $(LREF Expected) can be only constructed 1093 once and not modified afterwards. 1094 +/ 1095 immutable bool enableDefaultConstructor = false; 1096 1097 /// Handler for case when empty value is accessed. 1098 void onAccessEmptyValue(E)(E err) nothrow @nogc 1099 { 1100 assert(0, "Value not set"); 1101 } 1102 1103 /// Handler for case when empty error is accessed. 1104 void onAccessEmptyError() nothrow @nogc @safe 1105 { 1106 assert(0, "Error not set"); 1107 } 1108 } 1109 1110 /// 1111 @("Abort") 1112 @system unittest 1113 { 1114 static assert(!isDefaultConstructorEnabled!Abort); 1115 static assert(hasOnAccessEmptyValue!(Abort, string)); 1116 static assert(hasOnAccessEmptyValue!(Abort, int)); 1117 static assert(hasOnAccessEmptyError!Abort); 1118 1119 version (D_Exceptions) 1120 { 1121 assertThrown!Throwable(ok(42).error); 1122 assertThrown!Throwable(err!int("foo").value); 1123 } 1124 } 1125 1126 version (D_Exceptions) 1127 { 1128 /++ Hook implementation that throws exceptions instead of default assert behavior. 1129 +/ 1130 struct Throw 1131 { 1132 static: 1133 1134 /++ Default constructor for $(LREF Expected) is disabled. 1135 Same with the `opAssign`, so $(LREF Expected) can be only constructed 1136 once and not modified afterwards. 1137 +/ 1138 immutable bool enableDefaultConstructor = false; 1139 1140 /++ Handler for case when empty value is accessed. 1141 1142 Throws: 1143 If `E` inherits from `Throwable`, the error value is thrown. 1144 Otherwise, an [Unexpected] instance containing the error value is 1145 thrown. 1146 +/ 1147 void onAccessEmptyValue(E)(E err) 1148 { 1149 import std.traits : Unqual; 1150 static if(is(Unqual!E : Throwable)) throw err; 1151 else throw new Unexpected!E(err); 1152 } 1153 1154 /// Handler for case when empty error is accessed. 1155 void onAccessEmptyError() @safe 1156 { 1157 throw new Unexpected!string("Can't access error on expected value"); 1158 } 1159 } 1160 1161 /// 1162 @("Throw") 1163 @safe unittest 1164 { 1165 static assert(!isDefaultConstructorEnabled!Throw); 1166 static assert(hasOnAccessEmptyValue!(Throw, string)); 1167 static assert(hasOnAccessEmptyValue!(Throw, int)); 1168 static assert(hasOnAccessEmptyError!Throw); 1169 1170 assertThrown!(Unexpected!string)(ok!(string, Throw)(42).error); 1171 assertThrown!(Unexpected!string)(err!(int, Throw)("foo").value); 1172 assertThrown!(Unexpected!int)(err!(bool, Throw)(-1).value); 1173 } 1174 1175 /++ Hook implementation that behaves like a thrown exception. 1176 It throws $(D Exception) right when the $(LREF Expected) with error is initialized. 1177 1178 With this, one can easily change the code behavior between `Expected` idiom or plain `Exception`s. 1179 +/ 1180 struct AsException 1181 { 1182 static: 1183 1184 /++ Default constructor for $(LREF Expected) is disabled. 1185 Same with the `opAssign`, so $(LREF Expected) can be only constructed 1186 once and not modified afterwards. 1187 +/ 1188 immutable bool enableDefaultConstructor = false; 1189 1190 /++ Handler for case when empty error is accessed. 1191 +/ 1192 void onErrorSet(E)(auto ref E err) 1193 { 1194 import core.lifetime : forward; 1195 static if (is(E : Throwable)) throw E; 1196 else throw new Unexpected!E(forward!err); 1197 } 1198 } 1199 1200 /// 1201 @("AsException") 1202 @safe unittest 1203 { 1204 static assert(!isDefaultConstructorEnabled!AsException); 1205 static assert(hasOnErrorSet!(AsException, string)); 1206 1207 auto div(int a, int b) { 1208 if (b != 0) return ok!(string, AsException)(a / b); 1209 return err!(int, AsException)("oops"); 1210 } 1211 1212 assert(div(10, 2) == 5); 1213 assert(collectExceptionMsg!(Unexpected!string)(div(1, 0)) == "oops"); 1214 } 1215 } 1216 1217 /++ Hook implementation that behaves same as $(LREF Abort) hook, but uses refcounted payload 1218 instead, which also enables us to check, if the result was properly checked before it is 1219 discarded. 1220 +/ 1221 struct RCAbort 1222 { 1223 static: 1224 /++ Default constructor for $(LREF Expected) is disabled. 1225 Same with the `opAssign`, so $(LREF Expected) can be only constructed 1226 once and not modified afterwards. 1227 +/ 1228 immutable bool enableDefaultConstructor = false; 1229 1230 /// Copy constructor is enabled so the reference counting makes sense 1231 immutable bool enableCopyConstructor = true; 1232 1233 /// Enabled reference counted payload 1234 immutable bool enableRefCountedPayload = true; 1235 1236 void onUnchecked() pure nothrow @nogc @safe { assert(0, "result unchecked"); } 1237 } 1238 1239 /// 1240 @("RCAbort") 1241 @safe unittest 1242 { 1243 // behavior checks 1244 static assert(!isDefaultConstructorEnabled!RCAbort); 1245 static assert(isCopyConstructorEnabled!RCAbort); 1246 static assert(isRefCountedPayloadEnabled!RCAbort); 1247 1248 // basics 1249 assert(ok!(string, RCAbort)(42) == 42); 1250 assert(err!(int, RCAbort)("foo").error == "foo"); 1251 1252 // checked 1253 { 1254 auto res = ok!(string, RCAbort)(42); 1255 assert(!res.checked); 1256 assert(res); 1257 assert(res.checked); 1258 } 1259 1260 // unchecked - throws assert 1261 version (D_Exceptions) () @trusted { assertThrown!Throwable({ ok!(string, RCAbort)(42); }()); }(); 1262 1263 { 1264 auto res = ok!(string, RCAbort)(42); 1265 { 1266 auto res2 = res; 1267 assert(!res.checked); 1268 assert(res.refCount == 2); 1269 assert(res2.refCount == 2); 1270 } 1271 assert(res.refCount == 1); 1272 assert(res.hasValue); 1273 } 1274 1275 // chaining 1276 assert(err!(int, RCAbort)("foo").orElse!(() => ok!(string, RCAbort)(42)) == 42); 1277 assert(ok!(string, RCAbort)(42).andThen!(() => err!(int, RCAbort)("foo")).error == "foo"); 1278 version (D_Exceptions) 1279 { 1280 () @trusted 1281 { 1282 assertThrown!Throwable(err!(int, RCAbort)("foo").orElse!(() => ok!(string, RCAbort)(42))); 1283 assertThrown!Throwable(ok!(string, RCAbort)(42).andThen!(() => err!(int, RCAbort)("foo"))); 1284 }(); 1285 } 1286 } 1287 1288 version (D_Exceptions) 1289 { 1290 /++ An exception that represents an error value. 1291 1292 This is used by $(LREF Throw) hook when undefined value or error is 1293 accessed on $(LREF Expected) 1294 +/ 1295 class Unexpected(T) : Exception 1296 { 1297 // remove possible inout qualifier 1298 static if (is(T U == inout U)) alias ET = U; 1299 else alias ET = T; 1300 1301 ET error; /// error value 1302 1303 /// Constructs an `Unexpected` exception from an error value. 1304 pure @safe @nogc nothrow 1305 this()(auto ref T value, string file = __FILE__, size_t line = __LINE__) 1306 { 1307 import core.lifetime : forward; 1308 import std.traits : isAssignable; 1309 static if (isAssignable!(string, T)) super(forward!value, file, line); 1310 else super("Unexpected error", file, line); 1311 1312 this.error = error; 1313 } 1314 } 1315 } 1316 1317 /++ 1318 Creates an $(LREF Expected) object from an expected value, with type inference. 1319 +/ 1320 Expected!(T, E, Hook) ok(E = string, Hook = Abort, T)(auto ref T value) 1321 { 1322 import core.lifetime : forward; 1323 return Expected!(T, E, Hook)(forward!value); 1324 } 1325 1326 /// ditto 1327 Expected!(void, E, Hook) ok(E = string, Hook = Abort)() 1328 { 1329 return Expected!(void, E, Hook)(); 1330 } 1331 1332 /// 1333 @("Expected from value") 1334 @safe unittest 1335 { 1336 // void 1337 { 1338 auto res = ok(); 1339 static assert(is(typeof(res) == Expected!(void, string))); 1340 assert(res); 1341 } 1342 1343 // int 1344 { 1345 auto res = ok(42); 1346 static assert(is(typeof(res) == Expected!(int, string))); 1347 assert(res); 1348 assert(res.value == 42); 1349 } 1350 1351 // string 1352 { 1353 auto res = ok("42"); 1354 static assert(is(typeof(res) == Expected!(string, string))); 1355 assert(res); 1356 assert(res.value == "42"); 1357 } 1358 1359 // other error type 1360 { 1361 auto res = ok!bool(42); 1362 static assert(is(typeof(res) == Expected!(int, bool))); 1363 assert(res); 1364 assert(res.value == 42); 1365 } 1366 } 1367 1368 /++ Constructs $(LREF Expected) from the result of the provided function. 1369 1370 If the function is `nothrow`, it just returns it's result using $(LREF Expected). 1371 1372 If not, then it consumes it's possible $(D Exception) using `try catch` block and 1373 constructs $(LREF Expected) in regards of the result. 1374 +/ 1375 template consume(alias fun, Hook = Abort) 1376 { 1377 import core.lifetime : forward; 1378 auto consume(Args...)(auto ref Args args) if (is(typeof(fun(forward!args)))) 1379 { 1380 import std.traits : hasFunctionAttributes; 1381 1382 alias T = typeof(fun(forward!args)); 1383 static if (is(hasFunctionAttributes!(fun, "nothrow"))) return ok!Exception(fun(forward!args)); 1384 else 1385 { 1386 try return Expected!(T, Exception)(fun(forward!args)); 1387 catch (Exception ex) return err!T(ex); 1388 } 1389 } 1390 } 1391 1392 version (D_Exceptions) 1393 { 1394 /// 1395 @("consume from function call") 1396 @safe unittest 1397 { 1398 auto fn(int v) { if (v == 42) throw new Exception("don't panic"); return v; } 1399 1400 assert(consume!fn(1) == 1); 1401 assert(consume!fn(42).error.msg == "don't panic"); 1402 } 1403 } 1404 1405 /++ 1406 Creates an $(LREF Expected) object from an error value, with type inference. 1407 +/ 1408 Expected!(T, E, Hook) err(T = void, Hook = Abort, E)(auto ref E err) 1409 { 1410 import core.lifetime : forward; 1411 static if (Expected!(T, E, Hook).Types.length == 1 && !is(T == void)) 1412 return Expected!(T, E, Hook)(forward!err, false); 1413 else return Expected!(T, E, Hook)(forward!err); 1414 } 1415 1416 /// 1417 @("Expected from error value") 1418 @safe unittest 1419 { 1420 // implicit void value type 1421 { 1422 auto res = err("foo"); 1423 static assert(is(typeof(res) == Expected!(void, string))); 1424 assert(!res); 1425 assert(res.error == "foo"); 1426 } 1427 1428 // bool 1429 { 1430 auto res = err!int("42"); 1431 static assert(is(typeof(res) == Expected!(int, string))); 1432 assert(!res); 1433 assert(res.error == "42"); 1434 } 1435 1436 // other error type 1437 { 1438 auto res = err!bool(42); 1439 static assert(is(typeof(res) == Expected!(bool, int))); 1440 assert(!res); 1441 assert(res.error == 42); 1442 } 1443 } 1444 1445 /++ Unwraps a result, yielding the content of expected value. 1446 If there is none, or error value, it throws $(D assert(0)) with the provided message. 1447 1448 Params: 1449 res = $(LREF Expected) to check the result of 1450 msg = message to use with assert 1451 handler = custom handler to be called on error 1452 +/ 1453 auto ref T expect(EX : Expected!(T, E, H), T, E, H)(auto ref EX res, lazy string msg) 1454 { 1455 //TODO: hook for customization 1456 1457 static if (!is(T == void)) { 1458 if (res.hasValue) return forwardValue!res; 1459 } 1460 else { if (!res.hasError) return; } 1461 1462 version (D_BetterC) assert(0, msg); 1463 else 1464 { 1465 import std.format : format; 1466 if (res.hasError) assert(0, format!"%s: %s"(msg, res.error)); 1467 else assert(0, format!"%s: empty"(msg)); 1468 } 1469 } 1470 1471 /// ditto 1472 auto ref T expect(alias handler, EX : Expected!(T, E, H), T, E, H)(auto ref EX res) 1473 { 1474 static if (!is(T == void)) { if (res.hasValue) return forwardValue!res; } 1475 else { if (!res.hasError) return; } 1476 1477 static if (!is(typeof(handler(forwardError!res)) == noreturn) && !is(typeof(handler(forwardError!res)) == void)) 1478 return handler(forwardError!res); 1479 else 1480 { 1481 handler(forwardError!res); 1482 static if (__VERSION__ < 2096) { 1483 static if (!is(T == void)) 1484 return T.init; 1485 } else { 1486 static if (!is(T == void) && !is(typeof(handler(forwardError!res)) == noreturn)) // avoid 'statement is not reachable' 1487 return T.init; 1488 } 1489 } 1490 } 1491 1492 /// 1493 @("expect") 1494 @safe unittest 1495 { 1496 assert(ok(42).expect("oops") == 42); 1497 ok().expect("oops"); // void value 1498 1499 version (D_Exceptions) 1500 { 1501 () @trusted 1502 { 1503 assert(collectExceptionMsg!Throwable(Expected!int.init.expect("oops")) == "oops: empty"); 1504 assert(collectExceptionMsg!Throwable(err!int("foo").expect("oops")) == "oops: foo"); 1505 }(); 1506 } 1507 1508 assert(ok("foo").expect!(a => "bar") == "foo"); 1509 assert(err!string("foo").expect!(a => "bar") == "bar"); 1510 assert(err!string("foo").expect!((a) {}) is null); 1511 1512 static struct NonCopyable { @disable this(this); int foo; } 1513 assert(err!NonCopyable(42).expect!((e) {}) == NonCopyable.init); 1514 err!void(42).expect!((e) {}); 1515 ok!int().expect!(e => assert(0)); 1516 ok!int(42).expect!(e => assert(0)); 1517 } 1518 1519 /++ Unwraps a result, yielding the content of an error value. 1520 If there is none, or success value, it throws $(D assert(0)) with the provided message. 1521 1522 Params: 1523 res = $(LREF Expected) to check the result of 1524 msg = message to use with assert 1525 handler = custom handler to be called on value 1526 +/ 1527 auto ref E expectErr(EX : Expected!(T, E, H), T, E, H)(auto ref EX res, lazy string msg) 1528 { 1529 //TODO: hook for customization 1530 1531 if (res.hasError) return res.error; 1532 1533 version (D_BetterC) assert(0, msg); 1534 else 1535 { 1536 import std.format : format; 1537 static if (!is(T == void)) 1538 { 1539 if (res.hasValue) assert(0, format!"%s: %s"(msg, forwardValue!res)); 1540 } 1541 assert(0, format!"%s: empty"(msg)); 1542 } 1543 } 1544 1545 /// ditto 1546 auto ref E expectErr(alias handler, EX : Expected!(T, E, H), T, E, H)(auto ref EX res) 1547 { 1548 if (res.hasError) return res.error; 1549 1550 static if (!is(typeof(handler(forwardError!res)) == noreturn) && !is(typeof(handler(T.init)) == void)) 1551 { 1552 static if (!is(T == void)) return handler(res.hasValue ? forwardValue!res : T.init); 1553 else return handler(); 1554 } 1555 else 1556 { 1557 static if (!is(T == void)) handler(res.hasValue ? forwardValue!res : T.init); 1558 else handler(); 1559 static if (__VERSION__ < 2096) 1560 return E.init; 1561 else static if (!is(typeof(handler(forwardError!res)) == noreturn)) // avoid 'statement is not reachable' 1562 return E.init; 1563 } 1564 } 1565 1566 /// 1567 @("expectErr") 1568 @safe unittest 1569 { 1570 assert(err("foo").expectErr("oops") == "foo"); 1571 version (D_Exceptions) 1572 { 1573 () @trusted 1574 { 1575 assert(collectExceptionMsg!Throwable(Expected!int.init.expectErr("oops")) == "oops: empty"); 1576 assert(collectExceptionMsg!Throwable(ok(42).expectErr("oops")) == "oops: 42"); 1577 assert(collectExceptionMsg!Throwable(ok().expectErr("oops")) == "oops: empty"); // void value 1578 }(); 1579 } 1580 1581 assert(ok("foo").expectErr!(a => "bar") == "bar"); 1582 assert(err!string("foo").expectErr!(a => "bar") == "foo"); 1583 assert(ok!string("foo").expectErr!((a) {}) is null); 1584 assert(ok!string(42).expectErr!((a) {}) is null); 1585 err!int(42).expectErr!(a => assert(0)); 1586 } 1587 1588 /++ 1589 Returns the error contained within the $(LREF Expected) _and then_ another value if there's no error. 1590 This function can be used for control flow based on $(LREF Expected) values. 1591 1592 Predicate can accept no arguments, variable arguments, or previous result value with additional variable arguments. 1593 It must return $(LREF Expected) wth the same error type. But can provide different value type. 1594 1595 Params: 1596 exp = The $(LREF Expected) to call andThen on 1597 value = The value to return if there isn't an error 1598 pred = The predicate to call if the there isn't an error 1599 +/ 1600 auto ref andThen(EX : Expected!(T, E, H), VEX : Expected!(VT, E, H), T, VT, E, H)( 1601 auto ref EX exp, auto ref VEX value) 1602 { 1603 import core.lifetime : forward; 1604 static if (is(T == VT)) return exp.hasError ? exp : value; 1605 else return exp.hasError ? err!(VT, H)(forwardError!exp) : value; 1606 } 1607 1608 /// ditto 1609 auto ref andThen(alias pred, EX : Expected!(T, E, H), T, E, H, Args...)(auto ref EX exp, auto ref Args args) 1610 { 1611 import core.lifetime : forward; 1612 static if (!is(T == void) && is(typeof(pred(forwardValue!exp, forward!args)) : Expected!(VT, E, H), VT)) 1613 { 1614 static if (is(T == VT)) return exp.hasError ? exp : pred(forwardValue!exp, forward!args); 1615 else return exp.hasError ? err!(VT, H)(forwardError!exp) : pred(forwardValue!exp, forward!args); 1616 } 1617 else static if (is(typeof(pred(forward!args)) : Expected!(VT, E, H), VT)) 1618 { 1619 static if (is(T == VT)) return exp.hasError ? exp : pred(forward!args); 1620 else return exp.hasError ? err!(VT, H)(forwardError!exp) : pred(forward!args); 1621 } 1622 else 1623 { 1624 static assert(0, "Expected predicate of Expected type with optional args receiving previous value"); 1625 } 1626 } 1627 1628 /// 1629 @("andThen") 1630 @safe unittest 1631 { 1632 import std.format : format; 1633 1634 assert(ok(42).andThen(ok(1)) == 1); 1635 assert(ok(42).andThen!(() => ok(0)) == 0); 1636 assert(ok(42).andThen(err!int("foo")).error == "foo"); 1637 assert(ok(42).andThen!(() => err!int("foo")).error == "foo"); 1638 assert(err!int("foo").andThen(ok(42)).error == "foo"); 1639 assert(err!int("foo").andThen!(() => ok(42)).error == "foo"); 1640 assert(err!int("foo").andThen(err!int("bar")).error == "foo"); 1641 assert(err!int("foo").andThen!(() => err!int("bar")).error == "foo"); 1642 1643 // with void value 1644 assert(ok().andThen!(() => ok())); 1645 assert(ok().andThen!(() => err("foo")).error == "foo"); 1646 assert(err("foo").andThen!(() => ok()).error == "foo"); 1647 1648 // with different value type 1649 assert(ok(42).andThen(ok("foo")) == "foo"); 1650 assert(err!int("bug").andThen(ok("foo")).error == "bug"); 1651 assert(ok(42).andThen!(() => err!bool("foo")).error == "foo"); 1652 assert(err!int("bug").andThen!(() => err!bool("foo")).error == "bug"); 1653 1654 // with args 1655 assert(ok(42).andThen!((v) => err!bool(v))("foo").error == "foo"); // doesn't use previous value 1656 version (D_BetterC) 1657 assert(ok(42).andThen!((i, v) 1658 { 1659 assert(i == 42); 1660 assert(v == "foo"); 1661 return err!bool("betterc"); 1662 })("foo").error == "betterc"); // pass previous value to predicate 1663 else 1664 assert(ok(42).andThen!((i, v) => err!bool(format!"%s: %s"(v, i)))("foo").error == "foo: 42"); // pass previous value to predicate 1665 assert(ok().andThen!((v) => ok(v))(42) == 42); // void type on first ok 1666 } 1667 1668 /++ 1669 Returns the value contained within the $(LREF Expected) _or else_ another value if there's an error. 1670 This function can be used for control flow based on $(LREF Expected) values. 1671 1672 Predicate can accept no arguments, variable arguments, or previous result error value with additional variable arguments. 1673 It must return $(LREF Expected) wth the same value type. But can provide different error value type. 1674 1675 Params: 1676 exp = The $(LREF Expected) to call orElse on 1677 value = The value to return if there is an error 1678 pred = The predicate to call if the there is an error 1679 +/ 1680 auto ref U orElse(EX, U)(auto ref EX exp, lazy U value) 1681 if (is(EX : Expected!(T, E, H), T, E, H) && is(U : T)) 1682 { 1683 import core.lifetime : forward; 1684 return forward!exp.orElse!value; 1685 } 1686 1687 /// ditto 1688 auto ref orElse(alias pred, EX : Expected!(T, E, H), T, E, H, Args...)( 1689 auto ref EX exp, auto ref Args args) 1690 { 1691 import core.lifetime : forward; 1692 static if (is(typeof(pred(forward!args)) : T)) 1693 return exp.hasError ? pred(forward!args) : exp.value; 1694 else static if (is(typeof(pred(forwardError!exp, forward!args)) : T)) 1695 return exp.hasError ? pred(forwardError!exp, forward!args) : exp.value; 1696 else static if (is(typeof(pred(forward!args)) : Expected!(T, VE, H), VE)) 1697 { 1698 static if (is(E == VE)) return exp.hasError ? pred(forward!args) : exp; 1699 else return exp.hasError ? pred(forward!args) : ok!VE(forwardValue!exp); 1700 } 1701 else static if (is(typeof(pred(forwardError!exp, forward!args)) : Expected!(T, VE, H), VE)) 1702 { 1703 static if (is(E == VE)) return exp.hasError ? pred(forwardError!exp, forward!args) : exp; 1704 else return exp.hasError ? pred(forwardError!exp, forward!args) : ok!VE(forwardValue!exp); 1705 } 1706 else static assert(0, "Expecting predicate of same value type"); 1707 } 1708 1709 /// 1710 @("orElse") 1711 @safe unittest 1712 { 1713 assert(ok(42).orElse(0) == 42); 1714 assert(ok(42).orElse!(() => 0) == 42); 1715 assert(err!int("foo").orElse(0) == 0); 1716 assert(err!int("foo").orElse!(() => 0) == 0); 1717 assert(ok(42).orElse!(() => ok(0)) == 42); 1718 assert(err!int("foo").orElse!(() => ok(42)) == 42); 1719 assert(err!int("foo").orElse!(() => err!int("bar")).error == "bar"); 1720 1721 // with void value 1722 assert(ok().orElse!(() => err("foo"))); 1723 assert(err("foo").orElse!(() => ok())); 1724 assert(err("foo").orElse!(() => err("bar")).error == "bar"); 1725 1726 // with args 1727 assert(err!int("foo").orElse!((v) => v)(42) == 42); 1728 1729 // with different error type 1730 assert(err!int("foo").orElse!((v) => ok!int(v))(42).value == 42); // string -> int 1731 assert(err!int("foo").orElse!((v) => err!int(v))(42).error == 42); 1732 assert(err!int("foo").orElse!((e, v) => err!int(e.length + v))(42).error == 45); // with previous error 1733 } 1734 1735 /++ 1736 Applies a function to the expected value in an $(LREF Expected) object. 1737 1738 If no expected value is present, the original error value is passed through 1739 unchanged, and the function is not called. 1740 1741 Params: 1742 op = function called to map $(LREF Expected) value 1743 hook = use another hook for mapped $(LREF Expected) 1744 1745 Returns: 1746 A new $(LREF Expected) object containing the result. 1747 +/ 1748 template map(alias op, Hook = Abort) 1749 { 1750 /++ 1751 The actual `map` function. 1752 1753 Params: 1754 self = an [Expected] object 1755 +/ 1756 auto ref map(T, E, H)(auto ref Expected!(T, E, H) self) 1757 if ((is(T == void) && is(typeof(op()))) || (!is(T == void) && is(typeof(op(forwardValue!self))))) 1758 { 1759 static if (is(T == void)) alias U = typeof(op()); 1760 else alias U = typeof(op(forwardValue!self)); 1761 1762 if (self.hasError) return err!(U, Hook)(forwardError!self); 1763 else 1764 { 1765 static if (is(T == void)) return ok!(E, Hook)(op()); 1766 else return ok!(E, Hook)(op(forwardValue!self)); 1767 } 1768 } 1769 } 1770 1771 /// 1772 @("map") 1773 @safe unittest 1774 { 1775 { 1776 assert(ok(42).map!(a => a/2).value == 21); 1777 assert(ok().map!(() => 42).value == 42); 1778 assert(err!int("foo").map!(a => 42).error == "foo"); 1779 assert(err("foo").map!(() => 42).error == "foo"); 1780 } 1781 1782 // remap hook 1783 { 1784 static struct Hook {} 1785 auto res = ok(42).map!(a => a/2, Hook); 1786 assert(res == 21); 1787 static assert(is(typeof(res) == Expected!(int, string, Hook))); 1788 } 1789 } 1790 1791 /++ 1792 Applies a function to the expected error in an $(LREF Expected) object. 1793 1794 If no error is present, the original value is passed through 1795 unchanged, and the function is not called. 1796 1797 Params: 1798 op = function called to map $(LREF Expected) error 1799 hook = use another hook for mapped $(LREF Expected) 1800 1801 Returns: 1802 A new $(LREF Expected) object containing the result. 1803 +/ 1804 template mapError(alias op, Hook = Abort) 1805 { 1806 /++ 1807 The actual `mapError` function. 1808 1809 Params: 1810 self = an [Expected] object 1811 +/ 1812 auto ref mapError(T, E, H)(auto ref Expected!(T, E, H) self) 1813 if (is(typeof(op(forwardError!self)))) 1814 { 1815 alias U = typeof(op(forwardError!self)); 1816 1817 static if (!is(T == void)) 1818 { 1819 if (self.hasValue) return ok!(U, Hook)(forwardValue!self); 1820 } 1821 return err!(T, Hook)(op(forwardError!self)); 1822 } 1823 } 1824 1825 /// 1826 @("mapError") 1827 @safe unittest 1828 { 1829 { 1830 assert(ok(42).mapError!(e => e).value == 42); 1831 assert(err("foo").mapError!(e => 42).error == 42); 1832 version (D_Exceptions) assert(err("foo").mapError!(e => new Exception(e)).error.msg == "foo"); 1833 } 1834 1835 // remap hook 1836 { 1837 static struct Hook {} 1838 auto res = ok(42).mapError!(e => e, Hook); 1839 assert(res == 42); 1840 static assert(is(typeof(res) == Expected!(int, string, Hook))); 1841 1842 auto res2 = err!int("foo").mapError!(e => "bar", Hook); 1843 assert(res2.error == "bar"); 1844 static assert(is(typeof(res2) == Expected!(int, string, Hook))); 1845 } 1846 } 1847 1848 /++ 1849 Maps a `Expected<T, E>` to `U` by applying a function to a contained value, or a fallback function to a contained error value. 1850 1851 Both functions has to be of the same return type. 1852 1853 This function can be used to unpack a successful result while handling an error. 1854 1855 Params: 1856 valueOp = function called to map $(LREF Expected) value 1857 errorOp = function called to map $(LREF Expected) error 1858 hook = use another hook for mapped $(LREF Expected) 1859 1860 Returns: 1861 A new $(LREF Expected) object containing the result. 1862 +/ 1863 template mapOrElse(alias valueOp, alias errorOp) 1864 { 1865 /++ 1866 The actual `mapOrElse` function. 1867 1868 Params: 1869 self = an [Expected] object 1870 +/ 1871 auto ref mapOrElse(T, E, H)(auto ref Expected!(T, E, H) self) 1872 if ( 1873 is(typeof(errorOp(forwardError!self))) && 1874 ( 1875 (is(T == void) && is(typeof(valueOp()) == typeof(errorOp(forwardError!self)))) || 1876 (!is(T == void) && is(typeof(valueOp(self.value)) == typeof(errorOp(forwardError!self)))) 1877 ) 1878 ) 1879 { 1880 alias U = typeof(errorOp(forwardError!self)); 1881 1882 if (self.hasError) return errorOp(forwardError!self); 1883 else 1884 { 1885 static if (is(T == void)) return valueOp(); 1886 else return valueOp(forwardValue!self); 1887 } 1888 } 1889 } 1890 1891 /// 1892 @("mapOrElse") 1893 @safe unittest 1894 { 1895 assert(ok(42).mapOrElse!(v => v/2, e => 0) == 21); 1896 assert(ok().mapOrElse!(() => true, e => false)); 1897 assert(err!int("foo").mapOrElse!(v => v/2, e => 42) == 42); 1898 assert(!err("foo").mapOrElse!(() => true, e => false)); 1899 } 1900 1901 private template forwardMember(alias arg, string member) 1902 { 1903 import core.lifetime : move; 1904 // lvalue arg or non-moveable member (rvalue or const/immutable) 1905 static if (__traits(isRef, arg) || 1906 __traits(isOut, arg) || 1907 __traits(isLazy, arg) || 1908 !is(typeof(move(__traits(getMember, arg, member))))) 1909 @property auto ref forwardMember(){ return __traits(getMember, arg, member); } 1910 // rvalue arg and moveable member (mutable lvalue) 1911 else 1912 @property auto forwardMember(){ return move(__traits(getMember, arg, member)); } 1913 } 1914 1915 private alias forwardValue(alias arg) = forwardMember!(arg, "value"); 1916 private alias forwardError(alias arg) = forwardMember!(arg, "error"); 1917 1918 @("forwardMember") 1919 @safe unittest 1920 { 1921 struct A 1922 { 1923 int i; 1924 ~this() {} 1925 } 1926 struct S 1927 { 1928 A a; 1929 A rvalue() { return a; } 1930 ref A lvalue() { return a; } 1931 } 1932 1933 bool foo(T)(auto ref T val) 1934 { 1935 return __traits(isRef, val); 1936 } 1937 bool bar(string member, T)(auto ref T val, out int after) 1938 { 1939 auto res = foo(forwardMember!(val, member)); 1940 after = val.a.i; 1941 return res; 1942 } 1943 1944 int after; 1945 1946 // rvalue arg, member -> foo gets rvalue by move 1947 assert(bar!"a"(S(A(1729)), after) == false); 1948 assert(after == 0); 1949 1950 // rvalue arg, rvalue method -> foo gets rvalue as return value of `rvalue` method, no moves 1951 assert(bar!"rvalue"(S(A(1729)), after) == false); 1952 assert(after == 1729); 1953 1954 // rvalue arg, lvalue method -> foo gets rvalue by move 1955 assert(bar!"lvalue"(S(A(1729)), after) == false); 1956 assert(after == 0); 1957 1958 auto s = S(A(42)); 1959 1960 // lvalue arg, member -> foo gets lvalue 1961 assert(bar!"a"(s, after) == true); 1962 assert(after == 42); 1963 assert(s.a.i == 42); 1964 1965 // lvalue arg, rvalue method -> foo gets rvalue as return value of `rvalue` method, no moves 1966 assert(bar!"rvalue"(s, after) == false); 1967 assert(after == 42); 1968 assert(s.a.i == 42); 1969 1970 // lvalue arg, lvalue method -> foo gets lvalue 1971 assert(bar!"lvalue"(s, after) == true); 1972 assert(after == 42); 1973 assert(s.a.i == 42); 1974 } 1975 1976 static if (__VERSION__ < 2096) { 1977 private alias noreturn = void; 1978 }