1 /** 2 Variants 3 4 Copyright: 5 Copyright © 2023-2025, Kitsunebi Games 6 Copyright © 2023-2025, Inochi2D Project 7 8 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 9 Authors: 10 Luna Nielsen 11 */ 12 module nulib.data.variant; 13 import numem.core.traits; 14 import numem.core.exception; 15 import numem.core.hooks; 16 import numem.optional; 17 import numem.lifetime; 18 19 /** 20 A type which can contain any type in the language, stored on the heap. 21 22 Note: 23 The ownership of a variant depends on the API, the $(D Variant) itself 24 does not own its memory. You may call the $(D free) method to free any 25 memory owned by the variant. 26 */ 27 struct Variant { 28 private: 29 // Helpers that are not nogc, to allow usage with druntime. 30 static bool isType(T)(TypeInfo id) => id == typeid(T); 31 static string getTypeName(TypeInfo id) => id.toString(); 32 33 @nogc: 34 TypeInfo id; 35 void* data; 36 37 // Internal ctor 38 this(TypeInfo id, void* data) @trusted nothrow { 39 this.id = id; 40 this.data = data; 41 } 42 43 public: 44 alias isInitialized this; 45 46 /** 47 An empty variant. 48 */ 49 enum empty = Variant(null, null); 50 51 /** 52 Whether the variant is empty. 53 */ 54 @property bool isEmpty() @trusted nothrow pure => id is null || data is null; 55 56 /** 57 Whether the variant has data. 58 */ 59 @property bool isInitialized() @trusted nothrow pure => id !is null && data !is null; 60 61 /** 62 Constructor 63 */ 64 this(T)(auto ref T value) @trusted nothrow { 65 static if (is(T == Variant)) { 66 this.id = value.id; 67 this.data = value.data; 68 } else { 69 this.id = typeid(T); 70 static if (isHeapAllocated!T) 71 this.data = cast(void*)value; 72 else { 73 this.data = nu_malloc(T.sizeof); 74 *(cast(T*)this.data) = value.move(); 75 } 76 } 77 } 78 79 /** 80 Gets whether the variant contains the given type. 81 82 Returns: 83 Whether the variant is storing a type with the given 84 type's type id. 85 */ 86 bool has(T)() @trusted nothrow { 87 return id !is null && assumeNoThrowNoGC(&isType!T, id); 88 } 89 90 /** 91 Tries to get the content of the variant. 92 93 Returns: 94 A $(D Option) value either containing the value 95 or invalid state. 96 */ 97 Option!T tryGet(T)() @trusted nothrow { 98 if (!has!(T)()) 99 return none!T(); 100 101 static if (isHeapAllocated!T) 102 return some!T(cast(T)data); 103 else 104 return some!T(*cast(T*)data); 105 } 106 107 /** 108 Gets the content of the variant. 109 110 Note: 111 This function will fatally crash if the variant 112 doesn't contain a value of the given type! 113 */ 114 T get(T)() @trusted nothrow { 115 if (!has!(T)()) { 116 import nulib.string : nstring; 117 nu_fatal(nstring("Type mismatch between ", T.stringof, " and ", assumeNoThrowNoGC(&getTypeName, id), "!").take()); 118 } 119 120 static if (isHeapAllocated!T) 121 return cast(T)data; 122 else 123 return *cast(T*)data; 124 } 125 126 /// Allows assigning the variant to a value. 127 void opAssign(T)(auto ref T value) @trusted nothrow { 128 static if (is(T == Variant)) { 129 this.id = value.id; 130 this.data = value.data; 131 } else { 132 this.id = typeid(T); 133 static if (isHeapAllocated!T) 134 this.data = cast(void*)value; 135 else { 136 this.data = nu_malloc(T.sizeof); 137 *(cast(T*)this.data) = value.move(); 138 } 139 } 140 } 141 142 /** 143 Gets whether the variant is the same as another variant. 144 */ 145 bool opEquals(T)(T other) @trusted nothrow { 146 return other.data is data; 147 } 148 149 /** 150 Frees the memory stored in the variant. 151 152 Note: 153 This will NOT call any destructors on the type in 154 question. 155 */ 156 void free() @trusted nothrow { 157 this.id = null; 158 if (data) { 159 nu_free(data); 160 this.data = null; 161 } 162 } 163 } 164 165 @("Variant: get, has, assign") 166 unittest { 167 168 // ctor initialize. 169 Variant v = 42; 170 assert(v); 171 assert(v.has!int); 172 assert(!v.has!long); 173 assert(v.tryGet!int); 174 assert((v.tryGet!int).get == 42); 175 v.free(); 176 177 // assign initialize 178 v = 42; 179 assert(v); 180 assert(v.has!int); 181 assert(v.tryGet!int); 182 assert((v.tryGet!int).get == 42); 183 Variant v2 = v; 184 assert(v == v2); // Is it a reference to the same data? 185 186 // Free one and empty the other. 187 v.free(); 188 v2 = Variant.empty; 189 assert(v == v2 && !v); 190 } 191 192 @("Variant: arrays") 193 unittest { 194 Variant v = [0, 1, 2, 3].nu_dup(); 195 assert(v); 196 assert(v.has!(int[])); 197 assert(v.tryGet!(int[]).get == [0, 1, 2, 3]); 198 v.free(); 199 }