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 std.traits: isPointer; 8 9 /** 10 * This type represents a value which cannot be null by its contracts. 11 */ 12 struct Some(T) if (is(T == class) || isPointer!T) { 13 private: 14 T _value; 15 public: 16 /// Disable default construction for Some!T types. 17 @disable this(); 18 19 /** 20 * Construct this object by wrapping a given value. 21 * 22 * Params: 23 * value = The value to create the object with. 24 */ 25 @safe pure nothrow 26 this(U)(inout(U) value) inout if(is(U : T)) 27 in { 28 assert(value !is null, "A null value was given to Some."); 29 } body { 30 _value = value; 31 } 32 33 /** 34 * Get the value from this object. 35 * 36 * Returns: The value wrapped by this object. 37 */ 38 @safe pure nothrow 39 @property inout(T) get() inout 40 out(value) { 41 assert(value !is null, "Some returned null!"); 42 } body { 43 return _value; 44 } 45 46 /** 47 * Assign another value to this object. 48 * 49 * Params: 50 * value = The value to set. 51 */ 52 @safe pure nothrow 53 void opAssign(U)(U value) if(is(U : T)) 54 in { 55 assert(value !is null, "A null value was given to Some."); 56 } body { 57 _value = value; 58 } 59 60 /// Implicitly convert Some!T objects to T. 61 alias get this; 62 } 63 64 /** 65 * A helper function for constructing Some!T values. 66 * 67 * Params: 68 * value = A value to wrap. 69 * 70 * Returns: The value wrapped in a non-nullable type. 71 */ 72 @safe pure nothrow 73 inout(Some!T) some(T)(inout(T) value) { 74 return inout(Some!T)(value); 75 } 76 77 // Test basic usage. 78 unittest { 79 class Klass {} 80 struct Struct {} 81 82 Some!Klass k = new Klass(); 83 k = new Klass(); 84 85 Klass k2 = k; 86 87 Some!(Struct*) s = new Struct(); 88 89 Struct* s1 = s; 90 } 91 92 // Test immutable 93 unittest { 94 class Klass {} 95 96 immutable(Some!Klass) k = new immutable Klass(); 97 } 98 99 // Test class hierarchies. 100 unittest { 101 class Animal {} 102 class Dog : Animal {} 103 104 Some!Animal a = new Animal(); 105 a = new Dog(); 106 107 auto d = new Dog(); 108 109 d = cast(Dog) a; 110 111 assert(d !is null); 112 113 Some!Dog d2 = new Dog(); 114 115 Animal a2 = d2; 116 } 117 118 // Test conversion between wrapper types when wrapped types are compatible. 119 unittest { 120 class Animal {} 121 class Dog : Animal {} 122 123 Some!Animal a = new Animal(); 124 Some!Dog d = new Dog(); 125 126 a = d; 127 } 128 129 // Test the wrapper function. 130 unittest { 131 class Klass {} 132 133 auto m = some(new Klass()); 134 auto c = some(new const Klass()); 135 auto i = some(new immutable Klass()); 136 137 assert(is(typeof(m) == Some!Klass)); 138 assert(is(typeof(c) == const(Some!Klass))); 139 assert(is(typeof(i) == immutable(Some!Klass))); 140 } 141 142 /** 143 * This type represents an optional value for T. 144 * 145 * This is a means of explicitly dealing with null values in every case. 146 */ 147 struct Option(T) if(is(T == class) || isPointer!T) { 148 private: 149 T _value; 150 public: 151 /** 152 * Construct this object by wrapping a given value. 153 * 154 * Params: 155 * value = The value to create the object with. 156 */ 157 @safe pure nothrow 158 this(U)(inout(U) value) inout if(is(U : T)) { 159 _value = value; 160 } 161 162 /** 163 * Get the value from this object. 164 * 165 * Contracts ensure this value isn't null. 166 * 167 * Returns: Some value from this object. 168 */ 169 @safe pure nothrow 170 @property Some!T get() 171 in { 172 assert(_value !is null, "get called for a null Option type!"); 173 } body { 174 return Some!T(_value); 175 } 176 177 /// ditto 178 @trusted pure nothrow 179 @property const(Some!T) get() const 180 in { 181 assert(_value !is null, "get called for a null Option type!"); 182 } body { 183 return Some!T(cast(T) _value); 184 } 185 186 /// ditto 187 @trusted pure nothrow 188 @property immutable(Some!T) get() immutable 189 in { 190 assert(_value !is null, "get called for a null Option type!"); 191 } body { 192 return Some!T(cast(T) _value); 193 } 194 195 /** 196 * Returns: True if the value this option type does not hold a value. 197 */ 198 @trusted pure nothrow 199 @property bool isNull() const { 200 return _value is null; 201 } 202 203 /** 204 * Assign another value to this object. 205 * 206 * Params: 207 * value = The value to set. 208 */ 209 @safe pure nothrow 210 void opAssign(U)(U value) if(is(U : T)) { 211 _value = value; 212 } 213 214 /** 215 * Permit foreach over an option type. 216 * 217 * Example: 218 * --- 219 * Option!T value = null; 220 * 221 * foreach(someValue; value) { 222 * // We never enter this loop body. 223 * } 224 * 225 * value = new T(); 226 * 227 * foreach(someValue; value) { 228 * // We enter this loop body exactly once. 229 * } 230 * --- 231 */ 232 int opApply(int delegate(Some!T) dg) { 233 if (_value !is null) { 234 return dg(Some!T(_value)); 235 } 236 237 return 0; 238 } 239 240 /// ditto 241 int opApply(int delegate(const(Some!T)) dg) const { 242 if (_value !is null) { 243 return dg(cast(const) Some!T(cast(T) _value)); 244 } 245 246 return 0; 247 } 248 249 /// ditto 250 int opApply(int delegate(immutable(Some!T)) dg) immutable { 251 if (_value !is null) { 252 return dg(cast(immutable) Some!T(cast(T) _value)); 253 } 254 255 return 0; 256 } 257 258 /// Reverse iteration is exactly the same as forward iteration. 259 alias opApplyReverse = opApply; 260 } 261 262 /** 263 * A helper function for constructing Option!T values. 264 * 265 * Params: 266 * value = A value to wrap. 267 * 268 * Returns: The value wrapped in an option type. 269 */ 270 @safe pure nothrow 271 inout(Option!T) option(T)(inout(T) value) { 272 return inout(Option!T)(value); 273 } 274 275 /// ditto 276 @safe pure nothrow 277 inout(Option!T) option(T)(inout(Some!T) value) { 278 return option(value._value); 279 } 280 281 // Test basic usage for Option 282 unittest { 283 class Klass {} 284 struct Struct {} 285 286 Option!Klass k = new Klass(); 287 k = new Klass(); 288 289 Klass k2 = k.get; 290 291 Option!(Struct*) s = new Struct(); 292 293 Struct* s1 = s.get; 294 } 295 296 // Test class hierarchies for Option 297 unittest { 298 class Animal {} 299 class Dog : Animal {} 300 301 Option!Animal a = new Animal(); 302 a = new Dog(); 303 304 auto d = new Dog(); 305 306 d = cast(Dog) a.get; 307 308 assert(d !is null); 309 310 Option!Dog d2 = new Dog(); 311 312 Animal a2 = d2.get; 313 } 314 315 // Test get across constness. 316 unittest { 317 class Klass {} 318 319 Option!Klass m = new Klass(); 320 const Option!Klass c = new const Klass(); 321 immutable Option!Klass i = new immutable Klass(); 322 323 auto someM = m.get(); 324 auto someC = c.get(); 325 auto someI = i.get(); 326 327 assert(is(typeof(someM) == Some!Klass)); 328 assert(is(typeof(someC) == const(Some!Klass))); 329 assert(is(typeof(someI) == immutable(Some!Klass))); 330 } 331 332 // Test foreach on option across constness. 333 unittest { 334 class Klass {} 335 336 Option!Klass m = new Klass(); 337 const Option!Klass c = new const Klass(); 338 immutable Option!Klass i = new immutable Klass(); 339 340 size_t mCount = 0; 341 size_t cCount = 0; 342 size_t iCount = 0; 343 344 foreach(val; m) { 345 ++mCount; 346 } 347 348 foreach(val; c) { 349 ++cCount; 350 } 351 352 foreach(val; i) { 353 ++iCount; 354 } 355 356 assert(mCount == 1); 357 assert(cCount == 1); 358 assert(iCount == 1); 359 } 360 361 // Test empty foreach 362 unittest { 363 class Klass {} 364 365 Option!Klass m = null; 366 const Option!Klass c = null; 367 immutable Option!Klass i = null; 368 369 size_t mCount = 0; 370 size_t cCount = 0; 371 size_t iCount = 0; 372 373 foreach(val; m) { 374 ++mCount; 375 } 376 377 foreach(val; c) { 378 ++cCount; 379 } 380 381 foreach(val; i) { 382 ++iCount; 383 } 384 385 assert(mCount == 0); 386 assert(cCount == 0); 387 assert(iCount == 0); 388 } 389 390 // Test foreach_reverse on option across constness. 391 unittest { 392 class Klass {} 393 394 Option!Klass m = new Klass(); 395 const Option!Klass c = new const Klass(); 396 immutable Option!Klass i = new immutable Klass(); 397 398 size_t mCount = 0; 399 size_t cCount = 0; 400 size_t iCount = 0; 401 402 foreach_reverse(val; m) { 403 ++mCount; 404 } 405 406 foreach_reverse(val; c) { 407 ++cCount; 408 } 409 410 foreach_reverse(val; i) { 411 ++iCount; 412 } 413 414 assert(mCount == 1); 415 assert(cCount == 1); 416 assert(iCount == 1); 417 } 418 419 // Test empty foreach_reverse 420 unittest { 421 class Klass {} 422 423 Option!Klass m = null; 424 const Option!Klass c = null; 425 immutable Option!Klass i = null; 426 427 size_t mCount = 0; 428 size_t cCount = 0; 429 size_t iCount = 0; 430 431 foreach_reverse(val; m) { 432 ++mCount; 433 } 434 435 foreach_reverse(val; c) { 436 ++cCount; 437 } 438 439 foreach_reverse(val; i) { 440 ++iCount; 441 } 442 443 assert(mCount == 0); 444 assert(cCount == 0); 445 assert(iCount == 0); 446 } 447 448 // Test setting Option from Some 449 unittest { 450 class Klass {} 451 452 Option!Klass m = some(new Klass()); 453 const Option!Klass c = some(new const Klass()); 454 immutable Option!Klass i = some(new immutable Klass()); 455 456 Option!Klass m2 = option(some(new Klass())); 457 const Option!Klass c2 = option(some(new const Klass())); 458 immutable Option!Klass i2 = option(some(new immutable Klass())); 459 460 461 Option!Klass m3; 462 463 m3 = some(new Klass()); 464 } 465 466 // Test isNull 467 unittest { 468 class Klass {} 469 470 Option!Klass m; 471 472 assert(m.isNull); 473 474 m = new Klass(); 475 476 assert(!m.isNull); 477 } 478 479 /** 480 * This type represents a range over an optional type. 481 * 482 * This is a RandomAccessRange. 483 */ 484 struct OptionRange(T) if(is(T == class) || isPointer!T) { 485 private: 486 T _value; 487 public: 488 /** 489 * Construct this range by wrapping a given value. 490 * 491 * Params: 492 * value = The value to create the range with. 493 */ 494 @safe pure nothrow 495 this(U)(U value) if(is(U : T)) { 496 _value = value; 497 } 498 499 /// 500 @trusted pure nothrow 501 void popFront() 502 in { 503 assert(_value !is null, "Attempted to pop an empty range!"); 504 } body { 505 static if(is(T == const) || is(T == immutable)) { 506 // Force the pointer held here into being null. 507 *(cast(void**) &_value) = null; 508 } else { 509 _value = null; 510 } 511 } 512 513 /// 514 alias popBack = popFront; 515 516 /// 517 @safe pure nothrow 518 @property inout(T) front() inout { 519 return _value; 520 } 521 522 /// 523 alias back = front; 524 525 /// 526 @safe pure nothrow 527 @property bool empty() const { 528 return _value is null; 529 } 530 531 /// 532 @safe pure nothrow 533 @property typeof(this) save() { 534 return this; 535 } 536 537 /// 538 @safe pure nothrow 539 @property size_t length() const { 540 return _value !is null ? 1 : 0; 541 } 542 543 /// 544 @safe pure nothrow 545 inout(T) opIndex(size_t index) inout 546 in { 547 assert(index <= length, "Index out of bounds!"); 548 } body { 549 return _value; 550 } 551 } 552 553 /** 554 * Create an OptionRange from an Option type. 555 * 556 * The range shall be empty when the option has no value, 557 * and it shall have one item when the option has a value. 558 * 559 * Params: 560 * optionalValue = An optional value. 561 * 562 * Returns: A range of 0 or 1 values. 563 */ 564 @safe pure nothrow 565 OptionRange!T range(T)(Option!T optionalValue) { 566 if (optionalValue.isNull) { 567 return typeof(return).init; 568 } 569 570 return OptionRange!T(optionalValue.get); 571 } 572 573 /// ditto 574 @trusted pure nothrow 575 OptionRange!(const(T)) range(T)(const(Option!T) optionalValue) { 576 return cast(typeof(return)) range(cast(Option!T)(optionalValue)); 577 } 578 579 /// ditto 580 @trusted pure nothrow 581 OptionRange!(immutable(T)) range(T)(immutable(Option!T) optionalValue) { 582 return cast(typeof(return)) range(cast(Option!T)(optionalValue)); 583 } 584 585 // Test creating ranges from option types. 586 unittest { 587 class Klass {} 588 589 Option!Klass m = new Klass(); 590 const(Option!Klass) c = new const Klass(); 591 immutable(Option!Klass) i = new immutable Klass(); 592 593 auto mRange = m.range; 594 auto cRange = c.range; 595 auto iRange = i.range; 596 597 assert(!mRange.empty); 598 assert(!cRange.empty); 599 assert(!iRange.empty); 600 601 assert(mRange.length == 1); 602 assert(cRange.length == 1); 603 assert(iRange.length == 1); 604 605 assert(mRange[0] is m.get); 606 assert(cRange[0] is c.get); 607 assert(iRange[0] is i.get); 608 609 assert(mRange.front is mRange.back); 610 assert(cRange.front is cRange.back); 611 assert(iRange.front is iRange.back); 612 613 auto mRangeSave = mRange.save; 614 auto cRangeSave = cRange.save; 615 auto iRangeSave = iRange.save; 616 617 mRange.popFront(); 618 cRange.popFront(); 619 iRange.popFront(); 620 621 assert(mRange.empty); 622 assert(cRange.empty); 623 assert(iRange.empty); 624 625 assert(mRange.length == 0); 626 assert(cRange.length == 0); 627 assert(iRange.length == 0); 628 629 assert(!mRangeSave.empty); 630 assert(!cRangeSave.empty); 631 assert(!iRangeSave.empty); 632 } 633 634 unittest { 635 import std.range; 636 637 // Test that all of the essential properties hold for this type. 638 static assert(isInputRange!(OptionRange!(void*))); 639 static assert(isForwardRange!(OptionRange!(void*))); 640 static assert(isBidirectionalRange!(OptionRange!(void*))); 641 static assert(isRandomAccessRange!(OptionRange!(void*))); 642 static assert(!isInfinite!(OptionRange!(void*))); 643 } 644 645 646 // Test std.algorithm integration 647 unittest { 648 class Klass { 649 int x = 3; 650 } 651 652 import std.algorithm; 653 654 Option!Klass foo = new Klass(); 655 656 auto squareSum(R)(R range) { 657 return reduce!((x, y) => x + y)(0, range.map!(val => val.x * val.x)); 658 } 659 660 auto fooSum = squareSum(foo.range); 661 662 import std.stdio; 663 664 assert(fooSum == 9); 665 666 Option!Klass bar; 667 668 auto barSum = squareSum(bar.range); 669 670 assert(barSum == 0); 671 }