2 days ago

On Monday, 20 October 2025 at 10:38:23 UTC, Serg Gini wrote:

>

On Monday, 20 October 2025 at 09:50:35 UTC, Brother Bill wrote:

>

So all that guidance of tail appending using 'length' and 'capacity' is obsolete.
That is, if the capacity > length, then one can safely extend the tail by (capacity - length) elements.
The new advice is to just not append to slices either for the base array or any tail slice array. Otherwise, breakage or termination of slice sharing may result.

Do I have that correct?

But I'm not sure about slice capacity. it doesn't make any sense for me when it is non-zero
https://dlang.org/spec/arrays.html#capacity-reserve

I've played with the code and think I understand D's rules for sharing or breaking sharing.

  1. Extending the base dynamic array up to (capacity – length) will continue to maintain sharing with derived slices.
  2. Extending the base dynamic array beyond (capacity – length) will make a copy of the base dynamic array and break sharing.
  3. Extending a derived slice will make a copy of the derived slice and break sharing.

source/app.d

import std.stdio;

void main()
{
	extendingBaseArraySmallMaintainsSharing();
	extendingBaseArrayLargeDoesNotMaintainSharing();
	extendingSliceDoesNotMaintainSharing();
}

void extendingBaseArraySmallMaintainsSharing()
{
	writeln("=== extendingBaseArraySmallMaintainsSharing ===");
	int[] slice = [1, 3, 5, 7, 9, 11, 13, 15];
	int[] tailSlice = slice[$ / 2 .. $];
	writeln("slice: ", slice, " length: ", slice.length, " capacity: ", slice.capacity, ", &slice[4]: ", &slice[4]);
	writeln("tailSlice: ", tailSlice, " length: ", tailSlice.length, " capacity: ", tailSlice.capacity, " &tailSlice[0]: ", &tailSlice[0]);

	slice.length += (slice.capacity - slice.length); // extend to capacity
	writeln("slice after: ", slice, " length: ", slice.length, " capacity: ", slice.capacity, ", &slice[4]: ", &slice[4]);
	writeln("tailSlice after incrementing length by 1: ", tailSlice, " length: ", tailSlice.length, " capacity: ", tailSlice.capacity, " &tailSlice[0]: ", &tailSlice[0]);
	tailSlice[0] = 888;
	writeln("After tail slice length increase and changing tailSlice[0] to 888. ", " length: ", tailSlice.length, " capacity: ", tailSlice.capacity);
	writeln("tailSlice: ", tailSlice);
	writeln("slice    : ", slice);
	writeln;
}

void extendingBaseArrayLargeDoesNotMaintainSharing()
{
	writeln("=== extendingBaseArrayLargeDoesNotMaintainSharing ===");
	int[] slice = [1, 3, 5, 7, 9, 11, 13, 15];
	int[] tailSlice = slice[$ / 2 .. $];
	writeln("slice: ", slice, " length: ", slice.length, " capacity: ", slice.capacity, ", &slice[4]: ", &slice[4]);
	writeln("tailSlice: ", tailSlice, " length: ", tailSlice.length, " capacity: ", tailSlice.capacity, " &tailSlice[0]: ", &tailSlice[0]);

	slice.length += (1 + slice.capacity - slice.length); // extend just beyond capacity
	writeln("slice after: ", slice, " length: ", slice.length, " capacity: ", slice.capacity, ", &slice[4]: ", &slice[4]);
	writeln("tailSlice after incrementing length by 1: ", tailSlice, " length: ", tailSlice.length, " capacity: ", tailSlice.capacity, " &tailSlice[0]: ", &tailSlice[0]);
	tailSlice[0] = 888;
	writeln("After tail slice length increase and changing tailSlice[0] to 888. ", " length: ", tailSlice.length, " capacity: ", tailSlice.capacity);
	writeln("tailSlice: ", tailSlice);
	writeln("slice    : ", slice);
	writeln;
}

void extendingSliceDoesNotMaintainSharing()
{
	writeln("=== extendingSliceDoesNotMaintainSharing ===");
	int[] slice = [1, 3, 5, 7, 9, 11, 13, 15];
	int[] tailSlice = slice[$ / 2 .. $];
	writeln("slice: ", slice, " length: ", slice.length, " capacity: ", slice.capacity, ", &slice[4]: ", &slice[4]);
	writeln("tailSlice: ", tailSlice, " length: ", tailSlice.length, " capacity: ", tailSlice.capacity, " &tailSlice[0]: ", &tailSlice[0]);

	tailSlice.length += (tailSlice.capacity - tailSlice.length); // extend to capacity
	writeln("slice after: ", slice, " length: ", slice.length, " capacity: ", slice.capacity, ", &slice[4]: ", &slice[4]);
	writeln("tailSlice after incrementing length by 1: ", tailSlice, " length: ", tailSlice.length, " capacity: ", tailSlice.capacity, " &tailSlice[0]: ", &tailSlice[0]);
	tailSlice[0] = 888;
	writeln("After tail slice length increase and changing tailSlice[0] to 888. ", " length: ", tailSlice.length, " capacity: ", tailSlice.capacity);
	writeln("tailSlice: ", tailSlice);
	writeln("slice    : ", slice);
	writeln;
}

Console output:

=== extendingBaseArraySmallMaintainsSharing ===
slice: [1, 3, 5, 7, 9, 11, 13, 15] length: 8 capacity: 11, &slice[4]: 237935F1010
tailSlice: [9, 11, 13, 15] length: 4 capacity: 7 &tailSlice[0]: 237935F1010
slice after: [1, 3, 5, 7, 9, 11, 13, 15, 0, 0, 0] length: 11 capacity: 11, &slice[4]: 237935F1010
tailSlice after incrementing length by 1: [9, 11, 13, 15] length: 4 capacity: 0 &tailSlice[0]: 237935F1010
After tail slice length increase and changing tailSlice[0] to 888.  length: 4 capacity: 0
tailSlice: [888, 11, 13, 15]
slice    : [1, 3, 5, 7, 888, 11, 13, 15, 0, 0, 0]

=== extendingBaseArrayLargeDoesNotMaintainSharing ===
slice: [1, 3, 5, 7, 9, 11, 13, 15] length: 8 capacity: 11, &slice[4]: 237935F1040
tailSlice: [9, 11, 13, 15] length: 4 capacity: 7 &tailSlice[0]: 237935F1040
slice after: [1, 3, 5, 7, 9, 11, 13, 15, 0, 0, 0, 0] length: 12 capacity: 15, &slice[4]: 237935F2010
tailSlice after incrementing length by 1: [9, 11, 13, 15] length: 4 capacity: 7 &tailSlice[0]: 237935F1040
After tail slice length increase and changing tailSlice[0] to 888.  length: 4 capacity: 7
tailSlice: [888, 11, 13, 15]
slice    : [1, 3, 5, 7, 9, 11, 13, 15, 0, 0, 0, 0]

=== extendingSliceDoesNotMaintainSharing ===
slice: [1, 3, 5, 7, 9, 11, 13, 15] length: 8 capacity: 11, &slice[4]: 237935F1070
tailSlice: [9, 11, 13, 15] length: 4 capacity: 7 &tailSlice[0]: 237935F1070
slice after: [1, 3, 5, 7, 9, 11, 13, 15] length: 8 capacity: 11, &slice[4]: 237935F1070
tailSlice after incrementing length by 1: [9, 11, 13, 15, 0, 0, 0] length: 7 capacity: 7 &tailSlice[0]: 237935F0020
After tail slice length increase and changing tailSlice[0] to 888.  length: 7 capacity: 7
tailSlice: [888, 11, 13, 15, 0, 0, 0]
slice    : [1, 3, 5, 7, 9, 11, 13, 15]
2 days ago

On Sunday, 19 October 2025 at 20:44:39 UTC, Brother Bill wrote:

>

Console output:

slice: [1, 3, 5, 7, 9, 11, 13, 15, 17] length: 9 capacity: 11

slice: [1, 3, 5, 7, 9, 11, 13, 15, 17] length: 9 capacity: 11
tailSlice: [9, 11, 13, 15, 17] length: 5 capacity: 7 &tailSlice[0]: 26AAAA31040
tailSlice after incrementing length by 1: [9, 11, 13, 15, 17, 0] length: 6 capacity: 11 &tailSlice[0]: 26AAAA31060
After tail slice length increase and changing tailSlice[0] to 888.  length: 6 capacity: 11
tailSlice: [888, 11, 13, 15, 17, 0]
slice    : [1, 3, 5, 7, 9, 11, 13, 15, 17]

This is a bug, introduced in 2.111.0

If you use 2.110.0 you will get the expected behavior.

-Steve

1 day ago

On Monday, 20 October 2025 at 14:43:05 UTC, Steven Schveighoffer wrote:

>

This is a bug, introduced in 2.111.0

https://github.com/dlang/dmd/issues/22004

-Steve

1 day ago

On Monday, 20 October 2025 at 14:43:05 UTC, Steven Schveighoffer wrote:

>

On Sunday, 19 October 2025 at 20:44:39 UTC, Brother Bill wrote:

>

Console output:

slice: [1, 3, 5, 7, 9, 11, 13, 15, 17] length: 9 capacity: 11

slice: [1, 3, 5, 7, 9, 11, 13, 15, 17] length: 9 capacity: 11
tailSlice: [9, 11, 13, 15, 17] length: 5 capacity: 7 &tailSlice[0]: 26AAAA31040
tailSlice after incrementing length by 1: [9, 11, 13, 15, 17, 0] length: 6 capacity: 11 &tailSlice[0]: 26AAAA31060
After tail slice length increase and changing tailSlice[0] to 888.  length: 6 capacity: 11
tailSlice: [888, 11, 13, 15, 17, 0]
slice    : [1, 3, 5, 7, 9, 11, 13, 15, 17]

This is a bug, introduced in 2.111.0

If you use 2.110.0 you will get the expected behavior.

-Steve

someone swapped the tradeoffs then calling it a bug is misleading
if you disagree pls fix this bug

import std;
bool issubset(T)(T[] a,T[] b){
	T* c=min(&a[0],&b[0]);
	T* d=max(&a[$-1],&b[$-1]);
	return &a[0]==c&& &a[$-1]==d;
}
unittest{
	int[100] foo;
	assert(issubset(foo[10..20],foo[15..19]));
	assert( ! issubset(foo[50..100],foo[15..30]));
}
unittest{
	int[] data=[1,2,3];
	int[] tail=data[1..$];
	assert(issubset(data,tail));
	assert(data.capacity>0);
	assert(tail.capacity>0);
	data.length+=1;
	assert(data[$-1]==int.init);
	data[$-1]=4;
	assert(data[$-1]==4);
	assert(issubset(data,tail));
	assert(tail.capacity>0);
	tail.length+=1;
	assert(tail[$-1]==int.init);
	assert(issubset(data,tail));
}
1 day ago

On Monday, 20 October 2025 at 17:56:24 UTC, monkyyy wrote:

>

someone swapped the tradeoffs then calling it a bug is misleading
if you disagree pls fix this bug

import std;
bool issubset(T)(T[] a,T[] b){
	T* c=min(&a[0],&b[0]);
	T* d=max(&a[$-1],&b[$-1]);
	return &a[0]==c&& &a[$-1]==d;
}
unittest{
	int[100] foo;
	assert(issubset(foo[10..20],foo[15..19]));
	assert( ! issubset(foo[50..100],foo[15..30]));
}
unittest{
	int[] data=[1,2,3];
	int[] tail=data[1..$];
	assert(issubset(data,tail));
	assert(data.capacity>0);
	assert(tail.capacity>0);
	data.length+=1;
	assert(data[$-1]==int.init);
	data[$-1]=4;
	assert(data[$-1]==4);
	assert(issubset(data,tail));
	assert(tail.capacity>0);
	tail.length+=1;
	assert(tail[$-1]==int.init);
	assert(issubset(data,tail));
}

Hard to understand this code. If you think this is an issue, please file at https://github.com/dlang/dmd/issues

Please include explanatory information, and what you expect to happen.

-Steve

1 day ago

On Monday, 20 October 2025 at 22:56:25 UTC, Steven Schveighoffer wrote:

> >

data[$-1]=4;
assert(data[$-1]==4);
Hard to understand this code.

...

I shall strive for more simplicity in the future

>

If you think this is an issue, please file at https://github.com/dlang/dmd/issues

Please include explanatory information, and what you expect to happen.

-Steve

it would need a dip to fix and .... oh look I already suggested it: https://forum.dlang.org/post/galsogadinyablnlzynt@forum.dlang.org

1 day ago

On Monday, 20 October 2025 at 10:38:23 UTC, Serg Gini wrote:

>

But I'm not sure about slice capacity. it doesn't make any sense for me when it is non-zero
https://dlang.org/spec/arrays.html#capacity-reserve

Here is a radical proposal.

If the base dynamic array has an initial size and neither it, nor dependent slices can be increased in length, this is a great simplification, and will maintain sharing.
Dependent slices can be changed again and again, and it all works.

It is when allowing appending elements or increasing length of any slice, that things get squirrelly. Extending a non-tail slice forces it to break sharing. Extending a tail slice beyond (capacity - length) breaks sharing.

Once sharing is broken/terminated, the semantics of assignment change.
I would imagine that there are few algorithms that handle this neat and clean.

Consider a class NonBreakingSlices that has this general behavior:

  1. Constructor has initial length or initial elements.
    For simplification, we have slices of int only. Easy to extend with Templates.
    This creates a dynamic array to be shared by dependent slices.
    This foundation dynamic array is the only one allowed to grow.

  2. void addSlice(string name, size_t start, size_t after, string basedOn)
    This would establish name as the slice name, based on a parent slice or base dynamic array.

  3. void removeSlice(string name)
    Valid only if has no dependencies.

  4. int item(string name, size_t index);
    Getter

  5. void setItem(string name, size_t index, int newValue);
    Setter

  6. void extend(string name, size_t countItems);
    Valid only if name is a tail slice at this point.
    Would extend foundation dynamic array by countItems.
    Would extend slice "name" by countItems by adjusting internal length.

The benefit is that this class could enforce non breaking slices that can increase in length.

IMHO, breaking sharing should result in an exception, instead of copying the expanding slice to another memory location.

If there are valid cases where breaking sharing is intentional and useful, kindly share some of those scenarios.

1 day ago

On Tuesday, 21 October 2025 at 02:08:41 UTC, Brother Bill wrote:

>

If there are valid cases where breaking sharing is intentional and useful, kindly share some of those scenarios.

appending a null to a string for toStringz

1 day ago

On Tuesday, 21 October 2025 at 02:42:07 UTC, monkyyy wrote:

>

On Tuesday, 21 October 2025 at 02:08:41 UTC, Brother Bill wrote:

>

If there are valid cases where breaking sharing is intentional and useful, kindly share some of those scenarios.

appending a null to a string for toStringz

Do you mean appending an ASCII NUL character 0x00 to a string to make it C language friendly?

Or appending a null pointer to toStringz function?

Not following.

19 hours ago

On Tuesday, 21 October 2025 at 10:28:52 UTC, Brother Bill wrote:

>

On Tuesday, 21 October 2025 at 02:42:07 UTC, monkyyy wrote:

>

On Tuesday, 21 October 2025 at 02:08:41 UTC, Brother Bill wrote:

>

If there are valid cases where breaking sharing is intentional and useful, kindly share some of those scenarios.

appending a null to a string for toStringz

Do you mean appending an ASCII NUL character 0x00 to a string to make it C language friendly?

Or appending a null pointer to toStringz function?

Not following.

Perhaps Monkyyy means that instead of appending the 0 on an existing string, it's better to allocate a new string for it instead, because you now must reallocate the original if you append to it?

FWIW, this is not what happens in phobos toStringz, it just appends without concern of this use case.

-Steve