1 /**
2 	Utility functions for array processing
3 
4 	Copyright: © 2012 Sönke Ludwig
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Sönke Ludwig
7 */
8 module vibe.utils.array;
9 
10 import vibe.container.internal.utilallocator;
11 
12 import std.algorithm;
13 import std.range : isInputRange, isOutputRange;
14 import std.traits;
15 static import std.utf;
16 
17 
18 enum AppenderResetMode {
19 	keepData,
20 	freeData,
21 	reuseData
22 }
23 
24 struct AllocAppender(ArrayType : E[], E) {
25 	alias ElemType = Unqual!E;
26 
27 	static assert(!hasIndirections!E && !hasElaborateDestructor!E);
28 
29 	private {
30 		ElemType[] m_data;
31 		ElemType[] m_remaining;
32 		IAllocator m_alloc;
33 		static if (is(RCIAllocator))
34 			RCIAllocator m_rcAlloc;
35 		bool m_allocatedBuffer = false;
36 	}
37 
38 	this(IAllocator alloc, ElemType[] initial_buffer = null)
39 	@safe {
40 		m_alloc = alloc;
41 		m_data = initial_buffer;
42 		m_remaining = initial_buffer;
43 	}
44 
45 	static if (is(RCIAllocator)) {
46 		this(RCIAllocator alloc, ElemType[] initial_buffer = null)
47 		@safe {
48 			m_rcAlloc = alloc;
49 			m_data = initial_buffer;
50 			m_remaining = initial_buffer;
51 		}
52 	}
53 
54 	@disable this(this);
55 
56 	@property ArrayType data() { return cast(ArrayType)m_data[0 .. m_data.length - m_remaining.length]; }
57 
58 	void reset(AppenderResetMode reset_mode = AppenderResetMode.keepData)
59 	{
60 		if (reset_mode == AppenderResetMode.keepData) m_data = null;
61 		else if (reset_mode == AppenderResetMode.freeData) { if (m_allocatedBuffer) withAlloc!"deallocate"(m_data); m_data = null; }
62 		m_remaining = m_data;
63 	}
64 
65 	/** Grows the capacity of the internal buffer so that it can hold a minumum amount of elements.
66 
67 		Params:
68 			amount = The minimum amount of elements that shall be appendable without
69 				triggering a re-allocation.
70 
71 	*/
72 	void reserve(size_t amount)
73 	@safe {
74 		size_t nelems = m_data.length - m_remaining.length;
75 		if (!m_data.length) {
76 			m_data = () @trusted { return cast(ElemType[])withAlloc!"allocate"(amount*E.sizeof); } ();
77 			m_remaining = m_data;
78 			m_allocatedBuffer = true;
79 		}
80 		if (m_remaining.length < amount) {
81 			debug {
82 				import std.digest.crc;
83 				auto checksum = crc32Of(cast(const(ubyte)[])m_data[0 .. nelems]);
84 			}
85 			if (m_allocatedBuffer) () @trusted {
86 				auto vdata = cast(void[])m_data;
87 				withAlloc!"reallocate"(vdata, (nelems+amount)*E.sizeof);
88 				m_data = cast(ElemType[])vdata;
89 			} (); else {
90 				auto newdata = () @trusted { return cast(ElemType[])withAlloc!"allocate"((nelems+amount)*E.sizeof); } ();
91 				newdata[0 .. nelems] = m_data[0 .. nelems];
92 				m_data = newdata;
93 				m_allocatedBuffer = true;
94 			}
95 			debug assert(crc32Of(cast(const(ubyte)[])m_data[0 .. nelems]) == checksum);
96 		}
97 		m_remaining = m_data[nelems .. m_data.length];
98 	}
99 
100 	void put(E el)
101 	@safe {
102 		if( m_remaining.length == 0 ) grow(1);
103 		m_remaining[0] = el;
104 		m_remaining = m_remaining[1 .. $];
105 	}
106 
107 	void put(scope ArrayType arr)
108 	@safe {
109 		if (m_remaining.length < arr.length) grow(arr.length);
110 		m_remaining[0 .. arr.length] = arr[];
111 		m_remaining = m_remaining[arr.length .. $];
112 	}
113 
114 	static if( !hasAliasing!E ){
115 		void put(scope const(ElemType)[] arr) @trusted {
116 			put(cast(ArrayType)arr);
117 		}
118 	}
119 
120 	static if( is(ElemType == char) ){
121 		void put(dchar el)
122 		@trusted {
123 			if( el < 128 ) put(cast(char)el);
124 			else {
125 				char[4] buf;
126 				auto len = std.utf.encode(buf, el);
127 				put(cast(ArrayType)buf[0 .. len]);
128 			}
129 		}
130 	}
131 
132 	static if( is(ElemType == wchar) ){
133 		void put(dchar el)
134 		@trusted {
135 			if( el < 128 ) put(cast(wchar)el);
136 			else {
137 				wchar[3] buf;
138 				auto len = std.utf.encode(buf, el);
139 				put(cast(ArrayType)buf[0 .. len]);
140 			}
141 		}
142 	}
143 
144 	static if (!is(E == immutable) || !hasAliasing!E) {
145 		/** Appends a number of bytes in-place.
146 
147 			The delegate will get the memory slice of the memory that follows
148 			the already written data. Use `reserve` to ensure that this slice
149 			has enough room. The delegate should overwrite as much of the
150 			slice as desired and then has to return the number of elements
151 			that should be appended (counting from the start of the slice).
152 		*/
153 		void append(scope size_t delegate(scope ElemType[] dst) @safe del)
154 		{
155 			auto n = del(m_remaining);
156 			assert(n <= m_remaining.length);
157 			m_remaining = m_remaining[n .. $];
158 		}
159 		/// ditto
160 		void append(scope size_t delegate(scope ElemType[] dst) del)
161 		{
162 			auto n = del(m_remaining);
163 			assert(n <= m_remaining.length);
164 			m_remaining = m_remaining[n .. $];
165 		}
166 	}
167 
168 	void grow(size_t min_free)
169 	@safe {
170 		if( !m_data.length && min_free < 16 ) min_free = 16;
171 
172 		auto min_size = m_data.length + min_free - m_remaining.length;
173 		auto new_size = max(m_data.length, 16);
174 		while( new_size < min_size )
175 			new_size = (new_size * 3) / 2;
176 		reserve(new_size - m_data.length + m_remaining.length);
177 	}
178 
179 	private auto withAlloc(string method, ARGS...)(auto ref ARGS args)
180 	{
181 		static if (is(RCIAllocator)) {
182 			if (!m_rcAlloc.isNull) return __traits(getMember, m_rcAlloc, method)(args);
183 		}
184 		if (m_alloc) return __traits(getMember, m_alloc, method)(args);
185 		assert(false, "Using AllocAppender with no allocator set");
186 	}
187 }
188 
189 unittest {
190 	auto a = AllocAppender!string(theAllocator());
191 	a.put("Hello");
192 	a.put(' ');
193 	a.put("World");
194 	assert(a.data == "Hello World");
195 	a.reset();
196 	assert(a.data == "");
197 }
198 
199 unittest {
200 	char[4] buf;
201 	auto a = AllocAppender!string(theAllocator(), buf);
202 	a.put("He");
203 	assert(a.data == "He");
204 	assert(a.data.ptr == buf.ptr);
205 	a.put("ll");
206 	assert(a.data == "Hell");
207 	assert(a.data.ptr == buf.ptr);
208 	a.put('o');
209 	assert(a.data == "Hello");
210 	assert(a.data.ptr != buf.ptr);
211 }
212 
213 unittest {
214 	char[4] buf;
215 	auto a = AllocAppender!string(theAllocator(), buf);
216 	a.put("Hello");
217 	assert(a.data == "Hello");
218 	assert(a.data.ptr != buf.ptr);
219 }
220 
221 unittest {
222 	auto app = AllocAppender!(int[])(theAllocator);
223 	app.reserve(2);
224 	app.append((scope mem) {
225 		assert(mem.length >= 2);
226 		mem[0] = 1;
227 		mem[1] = 2;
228 		return size_t(2);
229 	});
230 	assert(app.data == [1, 2]);
231 }
232 
233 unittest {
234 	auto app = AllocAppender!string(theAllocator);
235 	app.reserve(3);
236 	app.append((scope mem) {
237 		assert(mem.length >= 3);
238 		mem[0] = 'f';
239 		mem[1] = 'o';
240 		mem[2] = 'o';
241 		return size_t(3);
242 	});
243 	assert(app.data == "foo");
244 }
245 
246 unittest {
247 	auto app = AllocAppender!string(theAllocator);
248 	app.reserve(3);
249 	app.put("foo");
250 	app.put("bar");
251 	assert(app.data == "foobar");
252 }
253 
254 
255 struct FixedAppender(ArrayType : E[], size_t NELEM, E) {
256 	alias ElemType = Unqual!E;
257 	private {
258 		ElemType[NELEM] m_data;
259 		size_t m_fill;
260 	}
261 
262 	void clear()
263 	{
264 		m_fill = 0;
265 	}
266 
267 	void put(E el)
268 	{
269 		m_data[m_fill++] = el;
270 	}
271 
272 	static if( is(ElemType == char) ){
273 		void put(dchar el)
274 		{
275 			if( el < 128 ) put(cast(char)el);
276 			else {
277 				char[4] buf;
278 				auto len = std.utf.encode(buf, el);
279 				put(cast(ArrayType)buf[0 .. len]);
280 			}
281 		}
282 	}
283 
284 	static if( is(ElemType == wchar) ){
285 		void put(dchar el)
286 		{
287 			if( el < 128 ) put(cast(wchar)el);
288 			else {
289 				wchar[3] buf;
290 				auto len = std.utf.encode(buf, el);
291 				put(cast(ArrayType)buf[0 .. len]);
292 			}
293 		}
294 	}
295 
296 	void put(ArrayType arr)
297 	{
298 		m_data[m_fill .. m_fill+arr.length] = arr[];
299 		m_fill += arr.length;
300 	}
301 
302 	@property ArrayType data() { return cast(ArrayType)m_data[0 .. m_fill]; }
303 
304 	static if (!is(E == immutable)) {
305 		void reset() { m_fill = 0; }
306 	}
307 }
308 
309 
310 /**
311 	TODO: clear ring buffer fields upon removal (to run struct destructors, if T is a struct)
312 */
313 deprecated("Use `vibe.container.ringbuffer.RingBuffer` instead.")
314 struct FixedRingBuffer(T, size_t N = 0, bool INITIALIZE = true) {
315 	private {
316 		static if( N > 0 ) {
317 			static if (INITIALIZE) T[N] m_buffer;
318 			else T[N] m_buffer = void;
319 		} else T[] m_buffer;
320 		size_t m_start = 0;
321 		size_t m_fill = 0;
322 	}
323 
324 	static if( N == 0 ){
325 		this(size_t capacity) { m_buffer = new T[capacity]; }
326 	}
327 
328 	@property bool empty() const { return m_fill == 0; }
329 
330 	@property bool full() const { return m_fill == m_buffer.length; }
331 
332 	@property size_t length() const { return m_fill; }
333 
334 	@property size_t freeSpace() const { return m_buffer.length - m_fill; }
335 
336 	@property size_t capacity() const { return m_buffer.length; }
337 
338 	static if( N == 0 ){
339 		/// Resets the capacity to zero and explicitly frees the memory for the buffer.
340 		void dispose()
341 		{
342 			import core.memory : __delete;
343 			__delete(m_buffer);
344 			m_buffer = null;
345 			m_start = m_fill = 0;
346 		}
347 
348 		@property void capacity(size_t new_size)
349 		{
350 			if( m_buffer.length ){
351 				auto newbuffer = new T[new_size];
352 				auto dst = newbuffer;
353 				auto newfill = min(m_fill, new_size);
354 				read(dst[0 .. newfill]);
355 				m_buffer = newbuffer;
356 				m_start = 0;
357 				m_fill = newfill;
358 			} else {
359 				m_buffer = new T[new_size];
360 			}
361 		}
362 	}
363 
364 	@property ref inout(T) front() inout { assert(!empty); return m_buffer[m_start]; }
365 
366 	@property ref inout(T) back() inout { assert(!empty); return m_buffer[mod(m_start+m_fill-1)]; }
367 
368 	void clear()
369 	{
370 		popFrontN(length);
371 		assert(m_fill == 0);
372 		m_start = 0;
373 	}
374 
375 	void putBack()(T itm) { assert(m_fill < m_buffer.length); m_buffer[mod(m_start + m_fill++)] = itm; }
376 	void putBack(TC : T)(TC[] itms)
377 	{
378 		if( !itms.length ) return;
379 		assert(m_fill+itms.length <= m_buffer.length);
380 		if( mod(m_start+m_fill) >= mod(m_start+m_fill+itms.length) ){
381 			size_t chunk1 = m_buffer.length - (m_start+m_fill);
382 			size_t chunk2 = itms.length - chunk1;
383 			m_buffer[m_start+m_fill .. m_buffer.length] = itms[0 .. chunk1];
384 			m_buffer[0 .. chunk2] = itms[chunk1 .. $];
385 		} else {
386 			m_buffer[mod(m_start+m_fill) .. mod(m_start+m_fill)+itms.length] = itms[];
387 		}
388 		m_fill += itms.length;
389 	}
390 	void putBackN(size_t n) { assert(m_fill+n <= m_buffer.length); m_fill += n; }
391 
392 	alias put = putBack;
393 	alias putN = putBackN;
394 
395 	void putFront(T itm)
396 	{
397 		assert(m_fill < m_buffer.length);
398 		m_start = mod(m_start + m_buffer.length - 1);
399 		m_fill++;
400 		m_buffer[m_start] = itm;
401 	}
402 
403 	void popFront() { assert(!empty); m_start = mod(m_start+1); m_fill--; }
404 	void popFrontN(size_t n) { assert(length >= n); m_start = mod(m_start + n); m_fill -= n; }
405 
406 	void popBack() { assert(!empty); m_fill--; }
407 	void popBackN(size_t n) { assert(length >= n); m_fill -= n; }
408 
409 	void removeAt(Range r)
410 	{
411 		assert(r.m_buffer is m_buffer);
412 		if (r.m_start == m_start) { popFront(); return; }
413 		if( m_start + m_fill > m_buffer.length ){
414 			assert(r.m_start > m_start && r.m_start < m_buffer.length || r.m_start < mod(m_start+m_fill));
415 			if( r.m_start > m_start ){
416 				foreach(i; r.m_start .. m_buffer.length-1)
417 					m_buffer[i] = m_buffer[i+1];
418 				m_buffer[$-1] = m_buffer[0];
419 				foreach(i; 0 .. mod(m_start + m_fill - 1))
420 					m_buffer[i] = m_buffer[i+1];
421 			} else {
422 				foreach(i; r.m_start .. mod(m_start + m_fill - 1))
423 					m_buffer[i] = m_buffer[i+1];
424 			}
425 		} else {
426 			assert(r.m_start >= m_start && r.m_start < m_start+m_fill);
427 			foreach(i; r.m_start .. m_start+m_fill-1)
428 				m_buffer[i] = m_buffer[i+1];
429 		}
430 		m_fill--;
431 		destroy(m_buffer[mod(m_start+m_fill)]); // TODO: only call destroy for non-POD T
432 	}
433 
434 	inout(T)[] peek() inout { return m_buffer[m_start .. min(m_start+m_fill, m_buffer.length)]; }
435 	T[] peekDst() {
436 		if( m_start + m_fill < m_buffer.length ) return m_buffer[m_start+m_fill .. $];
437 		else return m_buffer[mod(m_start+m_fill) .. m_start];
438 	}
439 
440 	void read(T[] dst)
441 	{
442 		assert(dst.length <= length);
443 		if( !dst.length ) return;
444 		if( mod(m_start) >= mod(m_start+dst.length) ){
445 			size_t chunk1 = m_buffer.length - m_start;
446 			size_t chunk2 = dst.length - chunk1;
447 			dst[0 .. chunk1] = m_buffer[m_start .. $];
448 			dst[chunk1 .. $] = m_buffer[0 .. chunk2];
449 		} else {
450 			dst[] = m_buffer[m_start .. m_start+dst.length];
451 		}
452 		popFrontN(dst.length);
453 	}
454 
455 	int opApply(scope int delegate(ref T itm)  @safe del)
456 	{
457 		if( m_start+m_fill > m_buffer.length ){
458 			foreach(i; m_start .. m_buffer.length)
459 				if( auto ret = del(m_buffer[i]) )
460 					return ret;
461 			foreach(i; 0 .. mod(m_start+m_fill))
462 				if( auto ret = del(m_buffer[i]) )
463 					return ret;
464 		} else {
465 			foreach(i; m_start .. m_start+m_fill)
466 				if( auto ret = del(m_buffer[i]) )
467 					return ret;
468 		}
469 		return 0;
470 	}
471 
472 	/// iterate through elements with index
473 	int opApply(scope int delegate(size_t i, ref T itm) @safe del)
474 	{
475 		if( m_start+m_fill > m_buffer.length ){
476 			foreach(i; m_start .. m_buffer.length)
477 				if( auto ret = del(i - m_start, m_buffer[i]) )
478 					return ret;
479 			foreach(i; 0 .. mod(m_start+m_fill))
480 				if( auto ret = del(i + m_buffer.length - m_start, m_buffer[i]) )
481 					return ret;
482 		} else {
483 			foreach(i; m_start .. m_start+m_fill)
484 				if( auto ret = del(i - m_start, m_buffer[i]) )
485 					return ret;
486 		}
487 		return 0;
488 	}
489 
490 	ref inout(T) opIndex(size_t idx) inout { assert(idx < length); return m_buffer[mod(m_start+idx)]; }
491 
492 	Range opSlice() { return Range(m_buffer, m_start, m_fill); }
493 
494 	Range opSlice(size_t from, size_t to)
495 	{
496 		assert(from <= to);
497 		assert(to <= m_fill);
498 		return Range(m_buffer, mod(m_start+from), to-from);
499 	}
500 
501 	size_t opDollar(size_t dim)() const if(dim == 0) { return length; }
502 
503 	private size_t mod(size_t n)
504 	const {
505 		static if( N == 0 ){
506 			/*static if(PotOnly){
507 				return x & (m_buffer.length-1);
508 			} else {*/
509 				return n % m_buffer.length;
510 			//}
511 		} else static if( ((N - 1) & N) == 0 ){
512 			return n & (N - 1);
513 		} else return n % N;
514 	}
515 
516 	static struct Range {
517 		private {
518 			T[] m_buffer;
519 			size_t m_start;
520 			size_t m_length;
521 		}
522 
523 		private this(T[] buffer, size_t start, size_t length)
524 		{
525 			m_buffer = buffer;
526 			m_start = start;
527 			m_length = length;
528 		}
529 
530 		@property bool empty() const { return m_length == 0; }
531 
532 		@property ref inout(T) front() inout { assert(!empty); return m_buffer[m_start]; }
533 
534 		void popFront()
535 		{
536 			assert(!empty);
537 			m_start++;
538 			m_length--;
539 			if( m_start >= m_buffer.length )
540 				m_start = 0;
541 		}
542 	}
543 }
544 
545 deprecated unittest {
546 	import std.range : only;
547 
548 	static assert(isInputRange!(FixedRingBuffer!int) && isOutputRange!(FixedRingBuffer!int, int));
549 
550 	FixedRingBuffer!(int, 5) buf;
551 	assert(buf.length == 0 && buf.freeSpace == 5); buf.put(1); // |1 . . . .
552 	assert(buf.length == 1 && buf.freeSpace == 4); buf.put(2); // |1 2 . . .
553 	assert(buf.length == 2 && buf.freeSpace == 3); buf.put(3); // |1 2 3 . .
554 	assert(buf.length == 3 && buf.freeSpace == 2); buf.put(4); // |1 2 3 4 .
555 	assert(buf.length == 4 && buf.freeSpace == 1); buf.put(5); // |1 2 3 4 5
556 	assert(buf.length == 5 && buf.freeSpace == 0);
557 	assert(buf.front == 1);
558 	buf.popFront(); // .|2 3 4 5
559 	assert(buf.front == 2);
560 	buf.popFrontN(2); // . . .|4 5
561 	assert(buf.front == 4);
562 	assert(buf.length == 2 && buf.freeSpace == 3);
563 	buf.put([6, 7, 8]); // 6 7 8|4 5
564 	assert(buf.length == 5 && buf.freeSpace == 0);
565 	int[5] dst;
566 	buf.read(dst); // . . .|. .
567 	assert(dst == [4, 5, 6, 7, 8]);
568 	assert(buf.length == 0 && buf.freeSpace == 5);
569 	buf.put([1, 2]); // . . .|1 2
570 	assert(buf.length == 2 && buf.freeSpace == 3);
571 	buf.read(dst[0 .. 2]); //|. . . . .
572 	assert(dst[0 .. 2] == [1, 2]);
573 
574 	buf.put([0, 0, 0, 1, 2]); //|0 0 0 1 2
575 	buf.popFrontN(2); //. .|0 1 2
576 	buf.put([3, 4]); // 3 4|0 1 2
577 	foreach(i, item; buf)
578 	{
579 		assert(i == item);
580 	}
581 
582 	assert(buf.front == 0);
583 	assert(buf.full);
584 	buf.removeAt(buf[0..1]); //4 .|1 2 3
585 	foreach(i, item; buf)
586 	{
587 		assert(i == item - 1);
588 	}
589 
590 	buf.put(5); // 4 5|1 2 3
591 	buf.removeAt(buf[3..4]); // 5 .|1 2 3
592 	assert(buf.front == 1); buf.popFront();
593 	assert(buf.front == 2); buf.popFront();
594 	assert(buf.front == 3); buf.popFront();
595 	assert(buf.front == 5); buf.popFront();
596 	assert(buf.empty);
597 
598 	buf.put(1);
599 	assert(buf.front == 1);
600 	buf.front = 2;
601 	assert(buf.front == 2);
602 	buf[].front = 3;
603 	assert(buf.front == 3);
604 
605 	buf.putFront(4);
606 	assert(buf.length == 2);
607 	assert(buf[].equal(only(4, 3)));
608 }
609 
610 deprecated
611 struct ArraySet(Key)
612 {
613 	private {
614 		alias GenAllocator = typeof(vibeThreadAllocator());
615 		GenAllocator AW(GenAllocator a) { return a; }
616 		alias AllocatorType = AffixAllocator!(GenAllocator, int);
617 		Key[4] m_staticEntries;
618 		Key[] m_entries;
619 		AllocatorType m_allocator;
620 	}
621 
622 	~this()
623 	@trusted {
624 		if (m_entries.ptr) {
625 			if (--allocator.prefix(m_entries) <= 0) {
626 				try allocator.dispose(m_entries);
627 				catch (Exception e) assert(false, e.msg); // should never happen
628 			}
629 		}
630 	}
631 
632 	this(this)
633 	@trusted {
634 		if (m_entries.ptr) {
635 			allocator.prefix(m_entries)++;
636 		}
637 	}
638 
639 	@property ArraySet dup()
640 	{
641 		ArraySet ret;
642 		ret.m_staticEntries = m_staticEntries;
643 		ret.m_allocator = m_allocator;
644 
645 		if (m_entries.length) {
646 			Key[] duped;
647 			() @trusted {
648 				try duped = allocator.makeArray!(Key)(m_entries.length);
649 				catch (Exception e) assert(false, e.msg);
650 				if (!duped.length)
651 					assert(false, "Failed to allocate memory for duplicated "~ArraySet.stringof);
652 				allocator.prefix(duped) = 1;
653 			} ();
654 			duped[] = m_entries[];
655 			ret.m_entries = duped;
656 		}
657 
658 		return ret;
659 	}
660 
661 	void setAllocator(GenAllocator allocator)
662 	in { assert(m_entries.ptr is null, "Cannot set allocator after elements have been inserted."); }
663 	do {
664 		m_allocator = AllocatorType(AW(allocator));
665 	}
666 
667 	bool opBinaryRight(string op)(Key key) if (op == "in") { return contains(key); }
668 
669 	int opApply(int delegate(ref Key) @safe del)
670 	{
671 		foreach (ref k; m_staticEntries)
672 			if (k != Key.init)
673 				if (auto ret = del(k))
674 					return ret;
675 		foreach (ref k; m_entries)
676 			if (k != Key.init)
677 				if (auto ret = del(k))
678 					return ret;
679 		return 0;
680 	}
681 
682 	bool contains(Key key)
683 	const {
684 		foreach (ref k; m_staticEntries) if (k == key) return true;
685 		foreach (ref k; m_entries) if (k == key) return true;
686 		return false;
687 	}
688 
689 	void insert(Key key)
690 	{
691 		if (contains(key)) return;
692 
693 		foreach (ref k; m_staticEntries)
694 			if (k == Key.init) {
695 				k = key;
696 				return;
697 			}
698 
699 		foreach (ref k; m_entries)
700 			if (k == Key.init) {
701 				k = key;
702 				return;
703 			}
704 
705 		size_t oldlen = m_entries.length;
706 
707 		() @trusted {
708 			try {
709 				if (!oldlen) {
710 					m_entries = allocator.makeArray!Key(64);
711 					assert(m_entries.length, "Failed to allocate memory for "~ArraySet.stringof);
712 					allocator.prefix(m_entries) = 1;
713 				} else {
714 					int oldrc = allocator.prefix(m_entries);
715 					if (!allocator.expandArray(m_entries, max(64, oldlen * 3 / 4)))
716 						assert(false, "Failed to allocate memory for "~ArraySet.stringof);
717 					allocator.prefix(m_entries) = oldrc;
718 				}
719 			} catch (Exception e) assert(false, e.msg);
720 		} ();
721 
722 		m_entries[oldlen] = key;
723 	}
724 
725 	void remove(Key key)
726 	{
727 		foreach (ref k; m_staticEntries) if (k == key) { k = Key.init; return; }
728 		foreach (ref k; m_entries) if (k == key) { k = Key.init; return; }
729 	}
730 
731 	ref allocator()
732 	nothrow @trusted {
733 		try {
734 			auto palloc = m_allocator._parent;
735 			if (isNull(palloc)) {
736 				assert(!isNull(vibeThreadAllocator), "No vibeThreadAllocator set!?");
737 				m_allocator = AllocatorType(AW(vibeThreadAllocator));
738 			}
739 		} catch (Exception e) assert(false, e.msg); // should never throw
740 		return m_allocator;
741 	}
742 
743 	private static bool isNull(GenAllocator a)
744 	{
745 		static if (is(RCIAllocator) && is(GenAllocator == RCIAllocator))
746 			return a.isNull;
747 		else return !a;
748 	}
749 }
750 
751 @safe nothrow unittest {
752 	ArraySet!int s;
753 	s.setAllocator(() @trusted { return Mallocator.instance.allocatorObject; } ());
754 
755 	ArraySet!int t;
756 	t = s;
757 
758 	s.insert(1);
759 	s.insert(2);
760 	s.insert(3);
761 	s.insert(4);
762 	assert(s.contains(1));
763 	assert(s.contains(2));
764 	assert(s.contains(3));
765 	assert(s.contains(4));
766 	assert(!t.contains(1));
767 
768 	s.insert(5);
769 	assert(s.contains(5));
770 
771 	t = s;
772 	assert(t.contains(5));
773 	assert(t.contains(1));
774 
775 	s.insert(6);
776 	assert(s.contains(6));
777 	assert(t.contains(6));
778 
779 	s = ArraySet!int.init;
780 	assert(!s.contains(1));
781 	assert(t.contains(1));
782 	assert(t.contains(6));
783 
784 	s = t.dup;
785 	assert(s.contains(1));
786 	assert(s.contains(6));
787 
788 	t.remove(1);
789 	assert(!t.contains(1));
790 	assert(s.contains(1));
791 	assert(t.contains(2));
792 	assert(t.contains(6));
793 
794 	t.remove(6);
795 	assert(!t.contains(6));
796 	assert(s.contains(6));
797 	assert(t.contains(5));
798 }