1 module nxt.owned; 2 3 /** Return wrapper around container `Container` that can be safely sliced, by 4 tracking number of read borrowed ranges and whether it's currently write 5 borrowed. 6 7 Only relevant when `Container` implements referenced access over 8 <ul> 9 <li> `opSlice` and 10 <li> `opIndex` 11 </ul> 12 13 TODO: Iterate and wrap all @unsafe accessors () and wrapped borrow 14 checks for all modifying members of `Container`? 15 */ 16 struct Owned(Container) 17 if (needsOwnership!Container) 18 { 19 import std.range.primitives : hasSlicing; 20 import std.traits : isMutable; 21 22 /// Type of range of `Container`. 23 alias Range = typeof(Container.init[]); 24 25 pragma(inline): 26 27 ~this() nothrow @nogc 28 { 29 assert(!_writeBorrowed, "This is still write-borrowed, cannot release!"); 30 assert(_readBorrowCount == 0, "This is still read-borrowed, cannot release!"); 31 } 32 33 /// Move `this` into a returned r-value. 34 typeof(this) move() 35 { 36 assert(!_writeBorrowed, "This is still write-borrowed, cannot move!"); 37 assert(_readBorrowCount == 0, "This is still read-borrowed, cannot move!"); 38 import core.lifetime : move; 39 return move(this); 40 } 41 42 /** Checked overload for `std.algorithm.mutation.move`. */ 43 void move(ref typeof(this) dst) pure nothrow @nogc 44 { 45 assert(!_writeBorrowed, "Source is still write-borrowed, cannot move!"); 46 assert(_readBorrowCount == 0, "Source is still read-borrowed, cannot move!"); 47 48 assert(!dst._writeBorrowed, "Destination is still write-borrowed, cannot move!"); 49 assert(dst._readBorrowCount == 0, "Destination is still read-borrowed, cannot move!"); 50 51 import core.lifetime : move; 52 move(this, dst); 53 } 54 55 /** Checked overload for `std.algorithm.mutation.moveEmplace`. */ 56 void moveEmplace(ref typeof(this) dst) pure nothrow @nogc 57 { 58 assert(!_writeBorrowed, "Source is still write-borrowed, cannot moveEmplace!"); 59 assert(_readBorrowCount == 0, "Source is still read-borrowed, cannot moveEmplace!"); 60 61 import core.lifetime : moveEmplace; 62 moveEmplace(this, dst); 63 } 64 65 static if (true/*TODO: hasUnsafeSlicing!Container*/) 66 { 67 import nxt.borrowed : ReadBorrowed, WriteBorrowed; 68 69 /+ TODO: can all these definitions be reduce somehow? +/ 70 71 /// Get full read-only slice. 72 ReadBorrowed!(Range, Owned) sliceRO() const @trusted 73 { 74 import core.internal.traits : Unqual; 75 assert(!_writeBorrowed, "This is already write-borrowed"); 76 return typeof(return)(_container.opSlice, 77 cast(Unqual!(typeof(this))*)(&this)); // trusted unconst casta 78 } 79 80 /// Get read-only slice in range `i` .. `j`. 81 ReadBorrowed!(Range, Owned) sliceRO(size_t i, size_t j) const @trusted 82 { 83 import core.internal.traits : Unqual; 84 assert(!_writeBorrowed, "This is already write-borrowed"); 85 return typeof(return)(_container.opSlice[i .. j], 86 cast(Unqual!(typeof(this))*)(&this)); // trusted unconst cast 87 } 88 89 /// Get full read-write slice. 90 WriteBorrowed!(Range, Owned) sliceRW() @trusted 91 { 92 assert(!_writeBorrowed, "This is already write-borrowed"); 93 assert(_readBorrowCount == 0, "This is already read-borrowed"); 94 return typeof(return)(_container.opSlice, &this); 95 } 96 97 /// Get read-write slice in range `i` .. `j`. 98 WriteBorrowed!(Range, Owned) sliceRW(size_t i, size_t j) @trusted 99 { 100 assert(!_writeBorrowed, "This is already write-borrowed"); 101 assert(_readBorrowCount == 0, "This is already read-borrowed"); 102 return typeof(return)(_container.opSlice[i .. j], &this); 103 } 104 105 /// Get read-only slice in range `i` .. `j`. 106 auto opSlice(size_t i, size_t j) const 107 { 108 return sliceRO(i, j); 109 } 110 /// Get read-write slice in range `i` .. `j`. 111 auto opSlice(size_t i, size_t j) 112 { 113 return sliceRW(i, j); 114 } 115 116 /// Get read-only full slice. 117 auto opSlice() const 118 { 119 return sliceRO(); 120 } 121 /// Get read-write full slice. 122 auto opSlice() 123 { 124 return sliceRW(); 125 } 126 } 127 128 pure nothrow @safe @nogc: 129 130 @property: 131 132 /// Returns: `true` iff `this` is either write or read borrowed. 133 bool isBorrowed() const { return _writeBorrowed || _readBorrowCount >= 1; } 134 135 /// Returns: `true` iff owned container is write borrowed. 136 bool isWriteBorrowed() const { return _writeBorrowed; } 137 138 /// Returns: number of read-only borrowers of owned container. 139 uint readBorrowCount() const { return _readBorrowCount; } 140 141 Container _container; /// wrapped container 142 alias _container this; 143 144 public: 145 bool _writeBorrowed = false; /// `true` iff `_container` is currently referred to 146 uint _readBorrowCount = 0; /// number of readable borrowers. TODO: use `size_t` minus one bit instead in `size_t _stats` 147 enum readBorrowCountMax = typeof(_readBorrowCount).max; 148 } 149 150 /** Checked overload for `std.algorithm.mutation.move`. 151 152 TODO: Can we somehow prevent users of Owned from accidentally using 153 `std.algorithm.mutation.move` instead of this wrapper? 154 */ 155 void move(Owner)(ref Owner src, ref Owner dst) pure nothrow @safe @nogc 156 if (is(Owner == Owned!(_), _)) 157 { 158 src.move(dst); // reuse member function 159 } 160 161 /** Checked overload for `std.algorithm.mutation.moveEmplace`. 162 163 TODO: Can we somehow prevent users of Owned from accidentally using 164 `std.algorithm.mutation.moveEmplace` instead of this wrapper? 165 */ 166 void moveEmplace(Owner)(ref Owner src, ref Owner dst) pure nothrow @safe @nogc 167 if (is(Owner == Owned!(_), _)) 168 { 169 src.moveEmplace(dst); // reuse member function 170 } 171 172 template needsOwnership(Container) 173 { 174 import std.range.primitives : hasSlicing; 175 /+ TODO: activate when array_ex : UniqueArray +/ 176 // enum needsOwnership = hasSlicing!Container; /+ TODO: extend to check if it's not @safe +/ 177 enum needsOwnership = is(Container == struct); 178 } 179 180 pure unittest { 181 alias A = UniqueArray!int; 182 const Owned!A co; // const owner 183 184 import std.traits : isMutable; 185 static assert(!isMutable!(typeof(co))); 186 187 const cos = co[]; 188 } 189 190 pure @safe unittest { 191 alias A = UniqueArray!int; 192 A a = A.init; 193 a = A.init; 194 /+ TODO: a ~= A.init; +/ 195 } 196 197 pure unittest { 198 import std.exception: assertThrown; 199 import core.exception : AssertError; 200 201 import nxt.borrowed : ReadBorrowed, WriteBorrowed; 202 203 alias A = UniqueArray!int; 204 205 Owned!A oa; 206 207 Owned!A ob; 208 oa.move(ob); // ok to move unborrowed 209 210 Owned!A od = void; 211 oa.moveEmplace(od); // ok to moveEmplace unborrowed 212 213 oa ~= 1; 214 oa ~= 2; 215 assert(oa[] == [1, 2]); 216 assert(oa[0 .. 1] == [1]); 217 assert(oa[1 .. 2] == [2]); 218 assert(oa[0 .. 2] == [1, 2]); 219 assert(!oa.isWriteBorrowed); 220 assert(!oa.isBorrowed); 221 assert(oa.readBorrowCount == 0); 222 223 { 224 const wb = oa.sliceRW; 225 226 Owned!A oc; 227 assertThrown!AssertError(oa.move()); // cannot move write borrowed 228 229 assert(wb.length == 2); 230 static assert(!__traits(compiles, { auto wc = wb; })); // write borrows cannot be copied 231 assert(oa.isBorrowed); 232 assert(oa.isWriteBorrowed); 233 assert(oa.readBorrowCount == 0); 234 assertThrown!AssertError(oa.opSlice); // one more write borrow is not allowed 235 } 236 237 // ok to write borrow again in separate scope 238 { 239 const wb = oa.sliceRW; 240 241 assert(wb.length == 2); 242 assert(oa.isBorrowed); 243 assert(oa.isWriteBorrowed); 244 assert(oa.readBorrowCount == 0); 245 } 246 247 // ok to write borrow again in separate scope 248 { 249 const wb = oa.sliceRW(0, 2); 250 assert(wb.length == 2); 251 assert(oa.isBorrowed); 252 assert(oa.isWriteBorrowed); 253 assert(oa.readBorrowCount == 0); 254 } 255 256 // multiple read-only borrows are allowed 257 { 258 const rb1 = oa.sliceRO; 259 260 Owned!A oc; 261 assertThrown!AssertError(oa.move(oc)); // cannot move read borrowed 262 263 assert(rb1.length == oa.length); 264 assert(oa.readBorrowCount == 1); 265 266 const rb2 = oa.sliceRO; 267 assert(rb2.length == oa.length); 268 assert(oa.readBorrowCount == 2); 269 270 const rb3 = oa.sliceRO; 271 assert(rb3.length == oa.length); 272 assert(oa.readBorrowCount == 3); 273 274 const rb_ = rb3; 275 assert(rb_.length == oa.length); 276 assert(oa.readBorrowCount == 4); 277 assertThrown!AssertError(oa.sliceRW); // single write borrow is not allowed 278 } 279 280 // test modification via write borrow 281 { 282 auto wb = oa.sliceRW; 283 wb[0] = 11; 284 wb[1] = 12; 285 assert(wb.length == oa.length); 286 assert(oa.isWriteBorrowed); 287 assert(oa.readBorrowCount == 0); 288 assertThrown!AssertError(oa.sliceRO); 289 } 290 assert(oa[] == [11, 12]); 291 assert(oa.sliceRO(0, 2) == [11, 12]); 292 293 // test mutable slice 294 static assert(is(typeof(oa.sliceRW()) == WriteBorrowed!(_), _...)); 295 static assert(is(typeof(oa[]) == WriteBorrowed!(_), _...)); 296 foreach (ref e; oa.sliceRW) 297 { 298 assertThrown!AssertError(oa.sliceRO); // one more write borrow is not allowed 299 assertThrown!AssertError(oa.sliceRW); // one more write borrow is not allowed 300 assertThrown!AssertError(oa[]); // one more write borrow is not allowed 301 } 302 303 // test readable slice 304 static assert(is(typeof(oa.sliceRO()) == ReadBorrowed!(_), _...)); 305 foreach (const ref e; oa.sliceRO) 306 { 307 assert(oa.sliceRO.length == oa.length); 308 assert(oa.sliceRO[0 .. 0].length == 0); 309 assert(oa.sliceRO[0 .. 1].length == 1); 310 assert(oa.sliceRO[0 .. 2].length == oa.length); 311 assertThrown!AssertError(oa.sliceRW); // write borrow during iteration is not allowed 312 assertThrown!AssertError(oa.move()); // move not allowed when borrowed 313 } 314 315 // move semantics 316 auto oaMove1 = oa.move(); 317 auto oaMove2 = oaMove1.move(); 318 assert(oaMove2[] == [11, 12]); 319 320 // constness propagation from owner to borrower 321 Owned!A mo; // mutable owner 322 assert(mo.sliceRO.ptr == mo.ptr); 323 assert(mo.sliceRO(0, 0).ptr == mo.ptr); 324 static assert(is(typeof(mo.sliceRO()) == ReadBorrowed!(_), _...)); 325 326 const Owned!A co; // const owner 327 assert(co.sliceRO.ptr == co.ptr); 328 static assert(is(typeof(co.sliceRO()) == ReadBorrowed!(_), _...)); 329 } 330 331 nothrow unittest { 332 import std.algorithm.sorting : sort; 333 alias E = int; 334 alias A = UniqueArray!E; 335 A a; 336 sort(a[]); /+ TODO: make this work +/ 337 } 338 339 // y = sort(x.move()), where x and y are instances of unsorted Array 340 @safe nothrow unittest { 341 import std.algorithm.sorting : sort; 342 import std.range.primitives : isInputRange, isForwardRange, isRandomAccessRange, hasSlicing; 343 344 alias E = int; 345 alias A = UniqueArray!E; 346 alias O = Owned!A; 347 348 O o; 349 o ~= [42, 43]; 350 351 assert(o.length == 2); 352 353 scope os = o.sliceRO; 354 355 alias OS = typeof(os); 356 static assert(isInputRange!(OS)); 357 static assert(isForwardRange!(OS)); 358 static assert(hasSlicing!(OS)); 359 static assert(isRandomAccessRange!OS); 360 361 assert(!os.empty); 362 assert(os.length == 2); 363 os.popFront(); 364 assert(!os.empty); 365 assert(os.length == 1); 366 os.popFront(); 367 assert(os.empty); 368 369 /+ TODO: scope oss = os[]; // no op +/ 370 /+ TODO: assert(oss.empty); +/ 371 } 372 373 // check write-borrow 374 @safe nothrow unittest { 375 import std.algorithm.sorting : sort; 376 import std.range.primitives : isInputRange, isForwardRange, isRandomAccessRange, hasSlicing; 377 378 alias E = int; 379 alias A = UniqueArray!E; 380 alias O = Owned!A; 381 382 const O co; 383 auto cos = co[0 .. 0]; 384 const ccos = co[0 .. 0]; 385 386 /+ TODO: const coc = co[].save(); +/ 387 388 O o; 389 o ~= [42, 43]; 390 auto os = o.sliceRW; 391 alias OS = typeof(os); 392 static assert(isInputRange!(OS)); 393 394 // static assert(isForwardRange!(OS)); 395 // static assert(hasSlicing!(OS)); 396 // static assert(isRandomAccessRange!OS); 397 // import std.algorithm.sorting : sort; 398 // sort(o[]); 399 } 400 401 version (unittest) 402 { 403 import nxt.container.dynamic_array : UniqueArray = DynamicArray; 404 import nxt.debugio; 405 }