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 @safe pure nothrow @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) @safe pure nothrow @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) @safe pure nothrow @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 { 182 alias A = UniqueArray!int; 183 const Owned!A co; // const owner 184 185 import std.traits : isMutable; 186 static assert(!isMutable!(typeof(co))); 187 188 const cos = co[]; 189 } 190 191 @safe pure unittest 192 { 193 alias A = UniqueArray!int; 194 A a = A.init; 195 a = A.init; 196 // TODO: a ~= A.init; 197 } 198 199 pure unittest 200 { 201 import std.exception: assertThrown; 202 import core.exception : AssertError; 203 204 import nxt.borrowed : ReadBorrowed, WriteBorrowed; 205 206 alias A = UniqueArray!int; 207 208 Owned!A oa; 209 210 Owned!A ob; 211 oa.move(ob); // ok to move unborrowed 212 213 Owned!A od = void; 214 oa.moveEmplace(od); // ok to moveEmplace unborrowed 215 216 oa ~= 1; 217 oa ~= 2; 218 assert(oa[] == [1, 2]); 219 assert(oa[0 .. 1] == [1]); 220 assert(oa[1 .. 2] == [2]); 221 assert(oa[0 .. 2] == [1, 2]); 222 assert(!oa.isWriteBorrowed); 223 assert(!oa.isBorrowed); 224 assert(oa.readBorrowCount == 0); 225 226 { 227 const wb = oa.sliceRW; 228 229 Owned!A oc; 230 assertThrown!AssertError(oa.move()); // cannot move write borrowed 231 232 assert(wb.length == 2); 233 static assert(!__traits(compiles, { auto wc = wb; })); // write borrows cannot be copied 234 assert(oa.isBorrowed); 235 assert(oa.isWriteBorrowed); 236 assert(oa.readBorrowCount == 0); 237 assertThrown!AssertError(oa.opSlice); // one more write borrow is not allowed 238 } 239 240 // ok to write borrow again in separate scope 241 { 242 const wb = oa.sliceRW; 243 244 assert(wb.length == 2); 245 assert(oa.isBorrowed); 246 assert(oa.isWriteBorrowed); 247 assert(oa.readBorrowCount == 0); 248 } 249 250 // ok to write borrow again in separate scope 251 { 252 const wb = oa.sliceRW(0, 2); 253 assert(wb.length == 2); 254 assert(oa.isBorrowed); 255 assert(oa.isWriteBorrowed); 256 assert(oa.readBorrowCount == 0); 257 } 258 259 // multiple read-only borrows are allowed 260 { 261 const rb1 = oa.sliceRO; 262 263 Owned!A oc; 264 assertThrown!AssertError(oa.move(oc)); // cannot move read borrowed 265 266 assert(rb1.length == oa.length); 267 assert(oa.readBorrowCount == 1); 268 269 const rb2 = oa.sliceRO; 270 assert(rb2.length == oa.length); 271 assert(oa.readBorrowCount == 2); 272 273 const rb3 = oa.sliceRO; 274 assert(rb3.length == oa.length); 275 assert(oa.readBorrowCount == 3); 276 277 const rb_ = rb3; 278 assert(rb_.length == oa.length); 279 assert(oa.readBorrowCount == 4); 280 assertThrown!AssertError(oa.sliceRW); // single write borrow is not allowed 281 } 282 283 // test modification via write borrow 284 { 285 auto wb = oa.sliceRW; 286 wb[0] = 11; 287 wb[1] = 12; 288 assert(wb.length == oa.length); 289 assert(oa.isWriteBorrowed); 290 assert(oa.readBorrowCount == 0); 291 assertThrown!AssertError(oa.sliceRO); 292 } 293 assert(oa[] == [11, 12]); 294 assert(oa.sliceRO(0, 2) == [11, 12]); 295 296 // test mutable slice 297 static assert(is(typeof(oa.sliceRW()) == WriteBorrowed!(_), _...)); 298 static assert(is(typeof(oa[]) == WriteBorrowed!(_), _...)); 299 foreach (ref e; oa.sliceRW) 300 { 301 assertThrown!AssertError(oa.sliceRO); // one more write borrow is not allowed 302 assertThrown!AssertError(oa.sliceRW); // one more write borrow is not allowed 303 assertThrown!AssertError(oa[]); // one more write borrow is not allowed 304 } 305 306 // test readable slice 307 static assert(is(typeof(oa.sliceRO()) == ReadBorrowed!(_), _...)); 308 foreach (const ref e; oa.sliceRO) 309 { 310 assert(oa.sliceRO.length == oa.length); 311 assert(oa.sliceRO[0 .. 0].length == 0); 312 assert(oa.sliceRO[0 .. 1].length == 1); 313 assert(oa.sliceRO[0 .. 2].length == oa.length); 314 assertThrown!AssertError(oa.sliceRW); // write borrow during iteration is not allowed 315 assertThrown!AssertError(oa.move()); // move not allowed when borrowed 316 } 317 318 // move semantics 319 auto oaMove1 = oa.move(); 320 auto oaMove2 = oaMove1.move(); 321 assert(oaMove2[] == [11, 12]); 322 323 // constness propagation from owner to borrower 324 Owned!A mo; // mutable owner 325 assert(mo.sliceRO.ptr == mo.ptr); 326 assert(mo.sliceRO(0, 0).ptr == mo.ptr); 327 static assert(is(typeof(mo.sliceRO()) == ReadBorrowed!(_), _...)); 328 329 const Owned!A co; // const owner 330 assert(co.sliceRO.ptr == co.ptr); 331 static assert(is(typeof(co.sliceRO()) == ReadBorrowed!(_), _...)); 332 } 333 334 nothrow unittest 335 { 336 import std.algorithm.sorting : sort; 337 alias E = int; 338 alias A = UniqueArray!E; 339 A a; 340 sort(a[]); // TODO: make this work 341 } 342 343 // y = sort(x.move()), where x and y are instances of unsorted Array 344 @safe nothrow unittest 345 { 346 import std.algorithm.sorting : sort; 347 import std.range.primitives : isInputRange, isForwardRange, isRandomAccessRange, hasSlicing; 348 349 alias E = int; 350 alias A = UniqueArray!E; 351 alias O = Owned!A; 352 353 O o; 354 o ~= [42, 43]; 355 356 assert(o.length == 2); 357 358 scope os = o.sliceRO; 359 360 alias OS = typeof(os); 361 static assert(isInputRange!(OS)); 362 static assert(isForwardRange!(OS)); 363 static assert(hasSlicing!(OS)); 364 static assert(isRandomAccessRange!OS); 365 366 assert(!os.empty); 367 assert(os.length == 2); 368 os.popFront(); 369 assert(!os.empty); 370 assert(os.length == 1); 371 os.popFront(); 372 assert(os.empty); 373 374 // TODO: scope oss = os[]; // no op 375 // TODO: assert(oss.empty); 376 } 377 378 // check write-borrow 379 @safe nothrow unittest 380 { 381 import std.algorithm.sorting : sort; 382 import std.range.primitives : isInputRange, isForwardRange, isRandomAccessRange, hasSlicing; 383 384 alias E = int; 385 alias A = UniqueArray!E; 386 alias O = Owned!A; 387 388 const O co; 389 auto cos = co[0 .. 0]; 390 const ccos = co[0 .. 0]; 391 392 // TODO: const coc = co[].save(); 393 394 O o; 395 o ~= [42, 43]; 396 auto os = o.sliceRW; 397 alias OS = typeof(os); 398 static assert(isInputRange!(OS)); 399 400 // static assert(isForwardRange!(OS)); 401 // static assert(hasSlicing!(OS)); 402 // static assert(isRandomAccessRange!OS); 403 // import std.algorithm.sorting : sort; 404 // sort(o[]); 405 } 406 407 version(unittest) 408 { 409 import nxt.container.dynamic_array : UniqueArray = DynamicArray; 410 import nxt.dbgio; 411 }