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