1 /* 2 * Copyright (C) 2023 Mai-Lapyst 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU Affero General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU Affero General Public License for more details. 13 * 14 * You should have received a copy of the GNU Affero General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 /** 19 * Module to provide optionals for dlang. 20 * 21 * License: $(HTTP https://www.gnu.org/licenses/agpl-3.0.html, AGPL 3.0). 22 * Copyright: Copyright (C) 2023 Mai-Lapyst 23 * Authors: $(HTTP codeark.it/Mai-Lapyst, Mai-Lapyst) 24 */ 25 module ministd.optional; 26 27 /// Exception when an Optional is None but a operation that assumes an Some was executed 28 class OptionalIsNoneException : Exception { 29 this (string s, string op) { 30 super("Optional " ~ s ~ " is none; operation " ~ op ~ " not permitted"); 31 } 32 } 33 34 /// ditto 35 alias OptionIsNoneException = OptionalIsNoneException; 36 37 /// An optional of type `T` 38 struct Optional(T) { 39 private T value; 40 private bool _isSome = false; 41 42 this(T value, bool isSome) @disable; 43 44 private this(T value) { 45 this.value = value; 46 this._isSome = true; 47 } 48 49 /** 50 * Takes the value out of the optional, leaving a None in it's place 51 * 52 * Returns: the value the optional holds 53 * 54 * Throws: $(REF OptionalIsNoneException) if the optional is a None instead of a Some 55 */ 56 T take() { 57 if (!_isSome) { 58 throw new OptionalIsNoneException(.stringof, ".take()"); 59 } 60 this._isSome = false; 61 return value; 62 } 63 64 /** 65 * Gets the value of the optional but leaving it as it. 66 * 67 * Returns: the value the optional holds 68 * 69 * Throws: $(REF OptionalIsNoneException) if the optional is a None instead of a Some 70 */ 71 T get() { 72 if (!_isSome) { 73 throw new OptionalIsNoneException(.stringof, ".take()"); 74 } 75 return value; 76 } 77 78 /// Returns true if the optional is a None; false otherwise 79 bool isNone() { 80 return !_isSome; 81 } 82 83 /// Returns true if the optional is a Some; false otherwise 84 bool isSome() { 85 return _isSome; 86 } 87 88 /** 89 * Creates a None 90 * 91 * Returns: A optional which is a None 92 */ 93 static Optional none() { 94 return Optional(); 95 } 96 97 /** 98 * Creates a Some 99 * 100 * Params: 101 * value = the value to use for the Some 102 * 103 * Returns: A optional which is a Some and holds the given value 104 */ 105 static Optional some(T value) { 106 return Optional(value); 107 } 108 } 109 110 /// ditto 111 alias Option = Optional; 112 113 unittest { 114 Optional!int maybe_int = Optional!int.none(); 115 try { 116 maybe_int.take(); 117 assert(0, "Optional.take() should throw a OptionalIsNoneException if it is a None"); 118 } catch (OptionalIsNoneException) {} 119 } 120 121 unittest { 122 Optional!int maybe_int = Optional!int.some(42); 123 try { 124 auto i = maybe_int.take(); 125 assert(i == 42, "Optional.take() should return the value hold by the Some"); 126 assert(maybe_int.isNone(), "Optional should be a None after call to Optional.take()"); 127 } catch (OptionalIsNoneException) { 128 assert(0, "Optional.take() should not throw a OptionalIsNoneException if it is a Some"); 129 } 130 }