Jump to page: 1 2 3
Thread overview
Debug help - Programming in D - extending tail slice terminates sharing unexpectedly. Page 69, 70
4 days ago
Brother Bill
4 days ago
monkyyy
4 days ago
Brother Bill
4 days ago
Brother Bill
4 days ago
monkyyy
4 days ago
Brother Bill
4 days ago
monkyyy
4 days ago
Kapendev
4 days ago
Brother Bill
4 days ago
Serg Gini
4 days ago
Brother Bill
3 days ago
Brother Bill
3 days ago
monkyyy
3 days ago
Brother Bill
2 days ago
monkyyy
2 days ago
monkyyy
2 days ago
Brother Bill
2 days ago
Andy Valencia
2 days ago
Brother Bill
3 days ago
monkyyy
3 days ago
monkyyy
4 days ago

The program starts with a dynamic slice of odd integers 1 thru 15.
Append 17 and reassign to slice.
Declare dynamic slice tailSlice of the second half of slice.
Check the length of tailSlice, which is 5; and capacity of tailSlice, which is 7.

Since capacity > length, we ought to be able to add two integers to tailSlice without terminating sharing of slices with slice.
But incrementing the length of tailSlice by 1 breaks sharing.
This is unexpected.

Am I missing something, or have I (unlikely) found a bug in the D compiler?
What is the explanation of this behavior?
I would expect that increasing the length of tailSlice ought to continue to have sharing with slice.

source/app.d

import std.stdio;

void main()
{
	int[] slice = [1, 3, 5, 7, 9, 11, 13, 15];

	slice = slice ~ 17;
	writeln("slice: ", slice, " length: ", slice.length, " capacity: ", slice.capacity);
	writeln();

	int[] tailSlice = slice[$ / 2 .. $];
	writeln("slice: ", slice, " length: ", slice.length, " capacity: ", slice.capacity);
	writeln("tailSlice: ", tailSlice, " length: ", tailSlice.length, " capacity: ", tailSlice.capacity, " &tailSlice[0]: ", &tailSlice[0]);
	tailSlice.length += 1; // fails here
	// tailSlice ~= 0; // also fails
	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);
}

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]

4 days ago

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

>

The program starts with a dynamic slice of odd integers 1 thru 15.
Append 17 and reassign to slice.
Declare dynamic slice tailSlice of the second half of slice.
Check the length of tailSlice, which is 5; and capacity of tailSlice, which is 7.

Since capacity > length, we ought to be able to add two integers to tailSlice without terminating sharing of slices with slice.
But incrementing the length of tailSlice by 1 breaks sharing.
This is unexpected.

Am I missing something, or have I (unlikely) found a bug in the D compiler?
What is the explanation of this behavior?
I would expect that increasing the length of tailSlice ought to continue to have sharing with slice.

source/app.d

import std.stdio;

void main()
{
	int[] slice = [1, 3, 5, 7, 9, 11, 13, 15];

	slice = slice ~ 17;
	writeln("slice: ", slice, " length: ", slice.length, " capacity: ", slice.capacity);
	writeln();

	int[] tailSlice = slice[$ / 2 .. $];
	writeln("slice: ", slice, " length: ", slice.length, " capacity: ", slice.capacity);
	writeln("tailSlice: ", tailSlice, " length: ", tailSlice.length, " capacity: ", tailSlice.capacity, " &tailSlice[0]: ", &tailSlice[0]);
	tailSlice.length += 1; // fails here
	// tailSlice ~= 0; // also fails
	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);
}

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]

Appending one slice sets the capacity to zero on other related slices; its best to treat slices as dynamic arrays or slices, not both. It will just be spooki action at a distance.

4 days ago

On Sunday, 19 October 2025 at 21:01:06 UTC, monkyyy wrote:

>

Appending one slice sets the capacity to zero on other related slices; its best to treat slices as dynamic arrays or slices, not both. It will just be spooki action at a distance.

I'm missing something.

Step 1 was create a dynamic array named slice with initial values.
At this point, slice does not have slice semantics.

Step 2 was to append a new element.
Does this convert slice into an actual slice, and if so, why?

I simplified the program, removing the part of extending slice.
It still breaks slice sharing...

void main()
{
	int[] slice = [1, 3, 5, 7, 9, 11, 13, 15];
	writeln("slice: ", slice, " length: ", slice.length, " capacity: ", slice.capacity);
	writeln();

	int[] tailSlice = slice[$ / 2 .. $];
	writeln("slice: ", slice, " length: ", slice.length, " capacity: ", slice.capacity);
	writeln("tailSlice: ", tailSlice, " length: ", tailSlice.length, " capacity: ", tailSlice.capacity, " &tailSlice[0]: ", &tailSlice[0]);
	tailSlice.length += 1; // fails here
	// tailSlice ~= 0; // also fails
	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);
}

Console output:

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

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

What will cause termination or breakage of tail slice sharing?

  1. It is clear that if capacity is 0, then expansion of that slice will break sharing.
  2. It is explained in Programming in D, that if length < capacity, then one can append or extend the length by up to (capacity - length), and sharing should continue.
    This is what I attempted to do, but failed.

Please guide me to best practices with slice sharing.

4 days ago

On Sunday, 19 October 2025 at 21:30:40 UTC, Brother Bill wrote:

>

Please guide me to best practices with slice sharing.

Treat it as a design flaw, if your project need both slice and dynamic arrays and your causing spooky bugs, consider wrappers for slicing that disables appending and dynamic arrays that slicing returns explicit slices; pass dynamic arrays by ref.

4 days ago

On Sunday, 19 October 2025 at 22:57:38 UTC, monkyyy wrote:

>

On Sunday, 19 October 2025 at 21:30:40 UTC, Brother Bill wrote:

>

Please guide me to best practices with slice sharing.

Treat it as a design flaw, if your project need both slice and dynamic arrays and your causing spooky bugs, consider wrappers for slicing that disables appending and dynamic arrays that slicing returns explicit slices; pass dynamic arrays by ref.

So dynamic base arrays and tail slices that can grow can result in spooky bugs.

So would you recommend that when playing with slices to not extend the tail?

The workaround for this would be after extending the tail to create a new dynamic array with the extended values, then add the slices back from scratch?

4 days ago

On Sunday, 19 October 2025 at 23:27:28 UTC, Brother Bill wrote:

>

On Sunday, 19 October 2025 at 22:57:38 UTC, monkyyy wrote:

>

On Sunday, 19 October 2025 at 21:30:40 UTC, Brother Bill wrote:

>

Please guide me to best practices with slice sharing.

Treat it as a design flaw, if your project need both slice and dynamic arrays and your causing spooky bugs, consider wrappers for slicing that disables appending and dynamic arrays that slicing returns explicit slices; pass dynamic arrays by ref.

So dynamic base arrays and tail slices that can grow can result in spooky bugs.

Its the confusion of the two into one type

>

So would you recommend that when playing with slices to not extend the tail?

If you extend the tail, I wouldnt call it a slice
Im not sure what you want from "tail slices" but maybe this:

struct tailsilce(T){
  T[]* data;
  T[] me; alias me this;
  this(ref T[] data_,int i=0,int j=-1){
    data=&data;
    me=data[i..j==-1?$:j];
  }
  opwhateveritiscalled("~=")(T a){
    data~=a;
  }
}
>

The workaround for this would be after extending the tail to create a new dynamic array with the extended values, then add the slices back from scratch?

They can just be colored, both views of [] work, its only the overlap that causes problems

4 days ago

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

>

But incrementing the length of tailSlice by 1 breaks sharing.
This is unexpected.

This is not really unexpected.
It's easier to think about it if you treat slices as structs with 2 members, a pointer and a length. The important member is the pointer.

int[] slice = [1, 3, 5, 7, 9, 11, 13, 15];

Here you create a "struct" with a pointer provided by the GC. The GC keeps track of the amount of items this pointer is pointing to. It's not really important how it is tracked. The safest assumption a user can make in any language is that the pointer owns the memory.

int[] tailSlice = slice[$ / 2 .. $];

Here you create a new "struct" whose pointer differs from slice. The layout of the data now looks like this:

`slice` pointer    : ...*########...
`tailSlice` pointer: .......*####...

The pointer of tailSlice doesn't own the memory. It's a view into the memory owned by the slice pointer.

tailSlice.length += 1;

You are now changing the length of struct tailSlice. There is really no good way to know if the pointer of tailSlice is an item of slice or if the last item of tailSlice is the last item of slice, so the GC will copy the data to a new memory block and append one new item.

That's kinda it. If you try to append or remove items in cases like this, you will probably get a new pointer. If you want to use a slice as a dynamic array, it's a good idea to pass it by ref or pointer. Hope this helps.

4 days ago

On Monday, 20 October 2025 at 09:03:28 UTC, Kapendev wrote:

>

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

>

But incrementing the length of tailSlice by 1 breaks sharing.
This is unexpected.

This is not really unexpected.
It's easier to think about it if you treat slices as structs with 2 members, a pointer and a length. The important member is the pointer.

int[] slice = [1, 3, 5, 7, 9, 11, 13, 15];

Here you create a "struct" with a pointer provided by the GC. The GC keeps track of the amount of items this pointer is pointing to. It's not really important how it is tracked. The safest assumption a user can make in any language is that the pointer owns the memory.

int[] tailSlice = slice[$ / 2 .. $];

Here you create a new "struct" whose pointer differs from slice. The layout of the data now looks like this:

`slice` pointer    : ...*########...
`tailSlice` pointer: .......*####...

The pointer of tailSlice doesn't own the memory. It's a view into the memory owned by the slice pointer.

tailSlice.length += 1;

You are now changing the length of struct tailSlice. There is really no good way to know if the pointer of tailSlice is an item of slice or if the last item of tailSlice is the last item of slice, so the GC will copy the data to a new memory block and append one new item.

That's kinda it. If you try to append or remove items in cases like this, you will probably get a new pointer. If you want to use a slice as a dynamic array, it's a good idea to pass it by ref or pointer. Hope this helps.

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?

4 days ago

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?

I've played a bit with code:

import std.stdio;

void main()
{
    int[] slice = [1, 3, 5, 7, 9, 11, 13, 15];
    int[] tailSlice = slice[$ / 2 .. $];
    writeln("slice: ", slice, " length: ", slice.length, " capacity: ", slice.capacity, "address: ", slice.ptr);
    writeln("tailSlice: ", tailSlice, " length: ", tailSlice.length, " capacity: ", tailSlice.capacity, " address: ", tailSlice.ptr);
    writeln("---");
    slice[6] = 99;
    tailSlice[1] = 55;
    writeln("slice: ", slice, " length: ", slice.length, " capacity: ", slice.capacity, "address: ", slice.ptr);
    writeln("tailSlice: ", tailSlice, " length: ", tailSlice.length, " capacity: ", tailSlice.capacity, " address: ", tailSlice.ptr);
    writeln("---");
    slice ~= 100;
    writeln("slice: ", slice, " length: ", slice.length, " capacity: ", slice.capacity, "address: ", slice.ptr);
    writeln("tailSlice: ", tailSlice, " length: ", tailSlice.length, " capacity: ", tailSlice.capacity, " address: ", tailSlice.ptr);
    writeln("---");
    slice[7] = 66;
    tailSlice[0] = 44;
    slice ~= 111;
    writeln("slice: ", slice, " length: ", slice.length, " capacity: ", slice.capacity, "address: ", slice.ptr);
    writeln("tailSlice: ", tailSlice, " length: ", tailSlice.length, " capacity: ", tailSlice.capacity, " address: ", tailSlice.ptr);
}
slice: [1, 3, 5, 7, 9, 11, 13, 15] length: 8 capacity: 11address: 7FAE68344000
tailSlice: [9, 11, 13, 15] length: 4 capacity: 7 address: 7FAE68344010
---
slice: [1, 3, 5, 7, 9, 55, 99, 15] length: 8 capacity: 11address: 7FAE68344000
tailSlice: [9, 55, 99, 15] length: 4 capacity: 7 address: 7FAE68344010
---
slice: [1, 3, 5, 7, 9, 55, 99, 15, 100] length: 9 capacity: 11address: 7FAE68344000
tailSlice: [9, 55, 99, 15] length: 4 capacity: 0 address: 7FAE68344010
---
slice: [1, 3, 5, 7, 44, 55, 99, 66, 100, 111] length: 10 capacity: 11address: 7FAE68344000
tailSlice: [44, 55, 99, 66] length: 4 capacity: 0 address: 7FAE68344010

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

« First   ‹ Prev
1 2 3