1 /** 2 * This module defines an Option!T type and a related Some!T type for 3 * dealing nullable data in a safe manner. 4 */ 5 module dstruct.option; 6 7 import dstruct.support; 8 9 import std.traits; 10 import std.typecons; 11 12 private struct SomeTypeMarker {} 13 14 private enum isSomeType(T) = is(typeof(T.marker) == SomeTypeMarker); 15 16 /** 17 * This type represents a value which cannot be null by its contracts. 18 */ 19 struct Some(T) if (is(T == class) || isPointer!T) { 20 private: 21 enum marker = SomeTypeMarker.init; 22 T _value; 23 public: 24 /// Disable default construction for Some!T types. 25 @disable this(); 26 27 /** 28 * Construct this object by wrapping a given value. 29 * 30 * Params: 31 * value = The value to create the object with. 32 */ 33 @nogc @safe pure nothrow 34 this(U)(inout(U) value) inout if(is(U : T)) 35 in { 36 assert(value !is null, "A null value was given to Some."); 37 } body { 38 static assert( 39 !is(U == typeof(null)), 40 "Some!(" ~ T.stringof ~ ") cannot be constructed with null." 41 ); 42 43 _value = value; 44 } 45 46 /** 47 * Get the value from this object. 48 * 49 * Returns: The value wrapped by this object. 50 */ 51 @nogc @safe pure nothrow 52 @property inout(T) get() inout 53 out(value) { 54 assert(value !is null, "Some returned null!"); 55 } body { 56 return _value; 57 } 58 59 /** 60 * Assign another value to this object. 61 * 62 * Params: 63 * value = The value to set. 64 */ 65 @nogc @safe pure nothrow 66 void opAssign(U)(U value) if(is(U : T)) 67 in { 68 assert(value !is null, "A null value was given to Some."); 69 } body { 70 static assert( 71 !is(U == typeof(null)), 72 "Some!(" ~ T.stringof ~ ") cannot be assigned to with null." 73 ); 74 75 _value = value; 76 } 77 78 /// Implicitly convert Some!T objects to T. 79 alias get this; 80 } 81 82 /** 83 * A helper function for constructing Some!T values. 84 * 85 * Params: 86 * value = A value to wrap. 87 * 88 * Returns: The value wrapped in a non-nullable type. 89 */ 90 @nogc @safe pure nothrow 91 inout(Some!T) some(T)(inout(T) value) if (is(T == class) || isPointer!T) { 92 return inout(Some!T)(value); 93 } 94 95 // Test basic usage. 96 unittest { 97 class Klass {} 98 struct Struct {} 99 100 Some!Klass k = new Klass(); 101 k = new Klass(); 102 103 Klass k2 = k; 104 105 Some!(Struct*) s = new Struct(); 106 107 Struct* s1 = s; 108 } 109 110 // Test immutable 111 unittest { 112 class Klass {} 113 114 immutable(Some!Klass) k = new immutable Klass(); 115 } 116 117 // Test class hierarchies. 118 unittest { 119 class Animal {} 120 class Dog : Animal {} 121 122 Some!Animal a = new Animal(); 123 a = new Dog(); 124 125 auto d = new Dog(); 126 127 d = cast(Dog) a; 128 129 assert(d !is null); 130 131 Some!Dog d2 = new Dog(); 132 133 Animal a2 = d2; 134 } 135 136 // Test conversion between wrapper types when wrapped types are compatible. 137 unittest { 138 class Animal {} 139 class Dog : Animal {} 140 141 Some!Animal a = new Animal(); 142 Some!Dog d = new Dog(); 143 144 a = d; 145 } 146 147 // Test the wrapper function. 148 unittest { 149 class Klass {} 150 151 auto m = some(new Klass()); 152 auto c = some(new const Klass()); 153 auto i = some(new immutable Klass()); 154 155 assert(is(typeof(m) == Some!Klass)); 156 assert(is(typeof(c) == const(Some!Klass))); 157 assert(is(typeof(i) == immutable(Some!Klass))); 158 } 159 160 /** 161 * This type represents an optional value for T. 162 * 163 * This is a means of explicitly dealing with null values in every case. 164 */ 165 struct Option(T) if(is(T == class) || isPointer!T) { 166 private: 167 T _value; 168 public: 169 /** 170 * Construct this object by wrapping a given value. 171 * 172 * Params: 173 * value = The value to create the object with. 174 */ 175 @nogc @safe pure nothrow 176 this(U)(inout(U) value) inout if(is(U : T)) { 177 _value = value; 178 } 179 180 /** 181 * Get the value from this object. 182 * 183 * Contracts ensure this value isn't null. 184 * 185 * Returns: Some value from this object. 186 */ 187 @nogc @safe pure nothrow 188 @property Some!T get() 189 in { 190 assert(_value !is null, "get called for a null Option type!"); 191 } body { 192 return Some!T(_value); 193 } 194 195 /// ditto 196 @nogc @trusted pure nothrow 197 @property const(Some!T) get() const 198 in { 199 assert(_value !is null, "get called for a null Option type!"); 200 } body { 201 return Some!T(cast(T) _value); 202 } 203 204 /// ditto 205 @nogc @trusted pure nothrow 206 @property immutable(Some!T) get() immutable 207 in { 208 assert(_value !is null, "get called for a null Option type!"); 209 } body { 210 return Some!T(cast(T) _value); 211 } 212 213 static if(is(T == class)) { 214 /** 215 * Given some type U, perform a dynamic cast on the class reference 216 * held within this optional value, and return a new optional value 217 * which may be null if the cast fails. 218 * 219 * Returns: A casted optional value. 220 */ 221 @nogc pure nothrow 222 inout(Option!U) dynamicCast(U)() inout { 223 return Option!U(cast(U) _value); 224 } 225 } 226 227 /** 228 * Return some value from this reference, or a default value 229 * by calling a callable argument. (function pointer, delegate, etc.) 230 * 231 * The delegate can return a nullable type, but if the type is null 232 * an assertion error will be triggered, supposing the program is 233 * running in debug mode. The delegate may also return a Some type. 234 * 235 * Params: 236 * dg = Some delegate returning a value. 237 * 238 * Returns: This value, if the delegate's value if it is null. 239 */ 240 Some!T or(DG)(DG dg) 241 if ( 242 isCallable!DG 243 && (ParameterTypeTuple!DG).length == 0 244 && is(ReturnType!DG : T) 245 ) { 246 if (_value is null) { 247 static if(isSomeType!(ReturnType!DG)) { 248 return cast(Some!T) dg(); 249 } else { 250 return Some!T(dg()); 251 } 252 } 253 254 return some(_value); 255 } 256 257 /// ditto 258 const(Some!T) or(DG)(DG dg) const 259 if ( 260 isCallable!DG 261 && (ParameterTypeTuple!DG).length == 0 262 && is(Unqual!(ReturnType!DG) : Unqual!T) 263 ) { 264 if (_value is null) { 265 static if(isSomeType!(ReturnType!DG)) { 266 return cast(const(Some!T)) dg(); 267 } else { 268 return const(Some!T)(dg()); 269 } 270 } 271 272 return some(_value); 273 } 274 275 /// ditto 276 immutable(Some!T) or(DG)(DG dg) immutable 277 if ( 278 isCallable!DG 279 && (ParameterTypeTuple!DG).length == 0 280 && is(Unqual!(ReturnType!DG) : Unqual!T) 281 ) { 282 if (_value is null) { 283 static if(isSomeType!(ReturnType!DG)) { 284 return cast(immutable(Some!T)) dg(); 285 } else { 286 return immutable(Some!T)(dg()); 287 } 288 } 289 290 return some(_value); 291 } 292 293 /** 294 * Returns: True if the value this option type does not hold a value. 295 */ 296 @nogc @safe pure nothrow 297 @property bool isNull() const { 298 return _value is null; 299 } 300 301 /** 302 * Assign another value to this object. 303 * 304 * Params: 305 * value = The value to set. 306 */ 307 @nogc @safe pure nothrow 308 void opAssign(U)(U value) if(is(U : T)) { 309 _value = value; 310 } 311 } 312 313 /** 314 * A helper function for constructing Option!T values. 315 * 316 * Params: 317 * value = A value to wrap. 318 * 319 * Returns: The value wrapped in an option type. 320 */ 321 @nogc @safe pure nothrow 322 inout(Option!T) option(T)(inout(T) value) if (is(T == class) || isPointer!T) { 323 return inout(Option!T)(value); 324 } 325 326 /// ditto 327 @nogc @safe pure nothrow 328 inout(Option!T) option(T)(inout(Some!T) value) { 329 return option(value._value); 330 } 331 332 // Test basic usage for Option 333 unittest { 334 class Klass {} 335 struct Struct {} 336 337 Option!Klass k = new Klass(); 338 k = new Klass(); 339 340 Klass k2 = k.get; 341 342 Option!(Struct*) s = new Struct(); 343 344 Struct* s1 = s.get; 345 } 346 347 // Test class hierarchies for Option 348 unittest { 349 class Animal {} 350 class Dog : Animal {} 351 352 Option!Animal a = new Animal(); 353 a = new Dog(); 354 355 auto d = new Dog(); 356 357 d = cast(Dog) a.get; 358 359 assert(d !is null); 360 361 Option!Dog d2 = new Dog(); 362 363 Animal a2 = d2.get; 364 } 365 366 // Test get across constness. 367 unittest { 368 class Klass {} 369 370 Option!Klass m = new Klass(); 371 const Option!Klass c = new const Klass(); 372 immutable Option!Klass i = new immutable Klass(); 373 374 auto someM = m.get(); 375 auto someC = c.get(); 376 auto someI = i.get(); 377 378 assert(is(typeof(someM) == Some!Klass)); 379 assert(is(typeof(someC) == const(Some!Klass))); 380 assert(is(typeof(someI) == immutable(Some!Klass))); 381 } 382 383 // Test dynamicCast across constness. 384 unittest { 385 class Klass {} 386 class SubKlass {} 387 388 Option!Klass m = new Klass(); 389 const Option!Klass c = new const Klass(); 390 immutable Option!Klass i = new immutable Klass(); 391 392 auto subM = m.dynamicCast!SubKlass; 393 auto subC = c.dynamicCast!SubKlass; 394 auto subI = i.dynamicCast!SubKlass; 395 396 assert(is(typeof(subM) == Option!SubKlass)); 397 assert(is(typeof(subC) == const(Option!SubKlass))); 398 assert(is(typeof(subI) == immutable(Option!SubKlass))); 399 } 400 401 // Test .or, with the nice type qualifiers. 402 unittest { 403 class Klass {} 404 405 @safe pure nothrow 406 void runTest() { 407 Option!Klass m; 408 const Option!Klass c; 409 immutable Option!Klass i; 410 411 auto someM = m.or(()=> some(new Klass())); 412 auto someC = c.or(()=> some(new const(Klass)())); 413 auto someI = i.or(()=> some(new immutable(Klass)())); 414 415 assert(is(typeof(someM) == Some!Klass)); 416 assert(is(typeof(someC) == const(Some!Klass))); 417 assert(is(typeof(someI) == immutable(Some!Klass))); 418 419 auto someOtherM = m.or(()=> new Klass()); 420 auto someOtherC = c.or(()=> new const(Klass)()); 421 auto someOtherI = i.or(()=> new immutable(Klass)()); 422 423 assert(is(typeof(someOtherM) == Some!Klass)); 424 assert(is(typeof(someOtherC) == const(Some!Klass))); 425 assert(is(typeof(someOtherI) == immutable(Some!Klass))); 426 } 427 428 runTest(); 429 } 430 431 // Test .or with subclasses 432 unittest { 433 class Klass {} 434 class SubKlass : Klass {} 435 436 @safe pure nothrow 437 void runTest() { 438 Option!Klass m; 439 const Option!Klass c; 440 immutable Option!Klass i; 441 442 auto someM = m.or(()=> some(new SubKlass())); 443 auto someC = c.or(()=> some(new const(SubKlass)())); 444 auto someI = i.or(()=> some(new immutable(SubKlass)())); 445 446 assert(is(typeof(someM) == Some!Klass)); 447 assert(is(typeof(someC) == const(Some!Klass))); 448 assert(is(typeof(someI) == immutable(Some!Klass))); 449 450 auto someOtherM = m.or(()=> new SubKlass()); 451 auto someOtherC = c.or(()=> new const(SubKlass)()); 452 auto someOtherI = i.or(()=> new immutable(SubKlass)()); 453 454 assert(is(typeof(someOtherM) == Some!Klass)); 455 assert(is(typeof(someOtherC) == const(Some!Klass))); 456 assert(is(typeof(someOtherI) == immutable(Some!Klass))); 457 } 458 459 runTest(); 460 } 461 462 // Test .or with bad functions 463 unittest { 464 class Klass {} 465 466 @system 467 Klass mutFunc() { 468 if (1 == 2) { 469 throw new Exception(""); 470 } 471 472 return new Klass(); 473 } 474 475 @system 476 const(Klass) constFunc() { 477 if (1 == 2) { 478 throw new Exception(""); 479 } 480 481 return new const(Klass)(); 482 } 483 484 @system 485 immutable(Klass) immutableFunc() { 486 if (1 == 2) { 487 throw new Exception(""); 488 } 489 490 return new immutable(Klass)(); 491 } 492 493 Option!Klass m; 494 const Option!Klass c; 495 immutable Option!Klass i; 496 497 auto someM = m.or(&mutFunc); 498 auto someC = c.or(&constFunc); 499 auto someI = i.or(&immutableFunc); 500 501 assert(is(typeof(someM) == Some!Klass)); 502 assert(is(typeof(someC) == const(Some!Klass))); 503 assert(is(typeof(someI) == immutable(Some!Klass))); 504 } 505 506 // Test setting Option from Some 507 unittest { 508 class Klass {} 509 510 Option!Klass m = some(new Klass()); 511 const Option!Klass c = some(new const Klass()); 512 immutable Option!Klass i = some(new immutable Klass()); 513 514 Option!Klass m2 = option(some(new Klass())); 515 const Option!Klass c2 = option(some(new const Klass())); 516 immutable Option!Klass i2 = option(some(new immutable Klass())); 517 518 Option!Klass m3; 519 520 m3 = some(new Klass()); 521 } 522 523 // Test isNull 524 unittest { 525 class Klass {} 526 527 Option!Klass m; 528 529 assert(m.isNull); 530 531 m = new Klass(); 532 533 assert(!m.isNull); 534 } 535 536 /** 537 * This type represents a range over an optional type. 538 * 539 * This is a RandomAccessRange. 540 */ 541 struct OptionRange(T) if(is(T == class) || isPointer!T) { 542 private: 543 T _value; 544 public: 545 /** 546 * Construct this range by wrapping a given value. 547 * 548 * Params: 549 * value = The value to create the range with. 550 */ 551 @nogc @safe pure nothrow 552 this(U)(U value) if(is(U : T)) { 553 _value = value; 554 } 555 556 /// 557 @nogc @trusted pure nothrow 558 void popFront() 559 in { 560 assert(_value !is null, "Attempted to pop an empty range!"); 561 } body { 562 static if(is(T == const) || is(T == immutable)) { 563 // Force the pointer held here into being null. 564 *(cast(void**) &_value) = null; 565 } else { 566 _value = null; 567 } 568 } 569 570 /// 571 alias popBack = popFront; 572 573 /// 574 @nogc @safe pure nothrow 575 @property inout(T) front() inout { 576 return _value; 577 } 578 579 /// 580 alias back = front; 581 582 /// 583 @nogc @safe pure nothrow 584 @property bool empty() const { 585 return _value is null; 586 } 587 588 /// 589 @nogc @safe pure nothrow 590 @property typeof(this) save() { 591 return this; 592 } 593 594 /// 595 @nogc @safe pure nothrow 596 @property size_t length() const { 597 return _value !is null ? 1 : 0; 598 } 599 600 /// 601 @nogc @safe pure nothrow 602 inout(T) opIndex(size_t index) inout 603 in { 604 assert(index <= length, "Index out of bounds!"); 605 } body { 606 return _value; 607 } 608 } 609 610 /** 611 * Create an OptionRange from an Option type. 612 * 613 * The range shall be empty when the option has no value, 614 * and it shall have one item when the option has a value. 615 * 616 * Params: 617 * optionalValue = An optional value. 618 * 619 * Returns: A range of 0 or 1 values. 620 */ 621 @nogc @safe pure nothrow 622 OptionRange!T range(T)(Option!T optionalValue) { 623 if (optionalValue.isNull) { 624 return typeof(return).init; 625 } 626 627 return OptionRange!T(optionalValue.get); 628 } 629 630 /// ditto 631 @nogc @trusted pure nothrow 632 OptionRange!(const(T)) range(T)(const(Option!T) optionalValue) { 633 return cast(typeof(return)) range(cast(Option!T)(optionalValue)); 634 } 635 636 /// ditto 637 @nogc @trusted pure nothrow 638 OptionRange!(immutable(T)) range(T)(immutable(Option!T) optionalValue) { 639 return cast(typeof(return)) range(cast(Option!T)(optionalValue)); 640 } 641 642 // Test creating ranges from option types. 643 unittest { 644 class Klass {} 645 646 Option!Klass m = new Klass(); 647 const(Option!Klass) c = new const Klass(); 648 immutable(Option!Klass) i = new immutable Klass(); 649 650 auto mRange = m.range; 651 auto cRange = c.range; 652 auto iRange = i.range; 653 654 assert(!mRange.empty); 655 assert(!cRange.empty); 656 assert(!iRange.empty); 657 658 assert(mRange.length == 1); 659 assert(cRange.length == 1); 660 assert(iRange.length == 1); 661 662 assert(mRange[0] is m.get); 663 assert(cRange[0] is c.get); 664 assert(iRange[0] is i.get); 665 666 assert(mRange.front is mRange.back); 667 assert(cRange.front is cRange.back); 668 assert(iRange.front is iRange.back); 669 670 auto mRangeSave = mRange.save; 671 auto cRangeSave = cRange.save; 672 auto iRangeSave = iRange.save; 673 674 mRange.popFront(); 675 cRange.popFront(); 676 iRange.popFront(); 677 678 assert(mRange.empty); 679 assert(cRange.empty); 680 assert(iRange.empty); 681 682 assert(mRange.length == 0); 683 assert(cRange.length == 0); 684 assert(iRange.length == 0); 685 686 assert(!mRangeSave.empty); 687 assert(!cRangeSave.empty); 688 assert(!iRangeSave.empty); 689 } 690 691 unittest { 692 import std.range; 693 694 // Test that all of the essential properties hold for this type. 695 static assert(isInputRange!(OptionRange!(void*))); 696 static assert(isForwardRange!(OptionRange!(void*))); 697 static assert(isBidirectionalRange!(OptionRange!(void*))); 698 static assert(isRandomAccessRange!(OptionRange!(void*))); 699 static assert(!isInfinite!(OptionRange!(void*))); 700 } 701 702 703 // Test std.algorithm integration 704 unittest { 705 class Klass { 706 int x = 3; 707 } 708 709 import std.algorithm; 710 711 Option!Klass foo = new Klass(); 712 713 auto squareSum(R)(R range) { 714 return reduce!((x, y) => x + y)(0, range.map!(val => val.x * val.x)); 715 } 716 717 auto fooSum = squareSum(foo.range); 718 719 import std.stdio; 720 721 assert(fooSum == 9); 722 723 Option!Klass bar; 724 725 auto barSum = squareSum(bar.range); 726 727 assert(barSum == 0); 728 } 729 730 unittest { 731 class Klass {} 732 class SubKlass : Klass {} 733 class SubSubKlass : SubKlass {} 734 735 Some!Klass nonNullValue = some(new Klass()); 736 737 Option!Klass optionalValue; 738 739 // You can check if the value is null. 740 assert(optionalValue.isNull); 741 742 // You can assign Some!T values to it. 743 optionalValue = nonNullValue; 744 745 assert(!optionalValue.isNull); 746 747 // You can assign regular values, including derived types. 748 // Dervied Some!T values will work too. 749 optionalValue = new SubKlass(); 750 751 assert(!optionalValue.isNull); 752 753 // You can get the value out as a type Some!T from it and cast it 754 // to the class type. The dynamic cast will be used. 755 assert(cast(SubKlass) optionalValue.get() !is null); 756 757 // Using the right dynamic cast means that the value from that can be null, 758 // when the dynamic cast fails. 759 assert(cast(SubSubKlass) optionalValue.get() is null); 760 761 // Or create a new optional value with a cast, which will also work 762 // when the optional value is null. 763 // 764 // This method will not exist for optional pointers. 765 Option!SubSubKlass subValue = optionalValue.dynamicCast!SubSubKlass; 766 767 assert(subValue.isNull); 768 769 // We can assign back to a regular class reference. 770 Klass regularReference; 771 772 // When the optional value is null, the range will by empty. 773 optionalValue = null; 774 775 foreach(value; optionalValue.range) { 776 regularReference = value; 777 } 778 779 assert(regularReference is null); 780 781 // We there's a value, the range will have length 1. 782 optionalValue = new Klass(); 783 784 foreach(value; optionalValue.range) { 785 regularReference = value; 786 } 787 788 assert(regularReference !is null); 789 790 optionalValue = null; 791 792 // Finally, we can use a method to use a default value. 793 // If the default is null, an assertion error will be thrown 794 // in debug mode. Any callable will work with .or, and the callable 795 // can also return a Some!T type. 796 Some!Klass someOtherValue = optionalValue.or(() => new SubKlass()); 797 }