A slice can lose capacity simply by calling a function

Ali Çehreli via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Sat May 2 01:21:14 PDT 2015


This is related to a discussion[1] that I had started recently but I 
will give an even shorter example here:

void main()
{
     // Two slices to all element
     auto a = [ 1, 2, 3, 4 ];
     auto b = a;

     // Initially, they both have capacity (strange! :) )
     assert(a.capacity == 7);
     assert(b.capacity == 7);

     // The first one that gets a new element gets the capacity
     b ~= 42;
     assert(a.capacity == 0);    // <-- a loses
     assert(b.capacity == 7);
}

The interesting thing is that this situation is the same as appending to 
a slice parameter:

void foo(int[] b)
{
     // Since stomping is prevented by the runtime, I am
     // foolishly assuming that I can freely append to my
     // parameter. Unfortunately, this action will cost the
     // original slice its capacity.
     b ~= 42;
}

void main()
{
     auto a = [ 1, 2, 3, 4 ];

     assert(a.capacity == 7);
     foo(a);
     assert(a.capacity == 0);    // <-- Capacity is gone :(
}

Note that the code above is about a function appending to a parameter 
for its own implementation. Otherwise, the appended element cannot be 
seen by the original slice anyway.

Also note that it would be the same if the parameter were const(int)[].

This is a new discovery of mine. Note that this is different from the 
non-determinism of when two slices stop sharing elements.[2] To me, this 
is very a strange consequence of passing a slice /by value/ despite the 
common expectation that by value leaves the original variable untouched. 
After this, I am tempted to come up with the following guideline.

(I am leaving 'immutable' and 'shared' out of this discussion.)

Guideline: Slice parameters should either be passed by reference to 
non-const or passed by value to const:

   1a) void foo(ref       int [] arr);  // can modify everything

   1b) void foo(ref const(int)[] arr);  // cannot modify elements

   2)  void foo(    const(int[]) arr);  // cannot affect anything
                                        // (even capacity)

Only then the caller can be sure that the capacity of the original slice 
will not change. (I am assuming that the function is smart enough not to 
call assumeUnique.)

Since 1a and 1b are by reference, the function would not append to the 
parameter for its own implementation purposes anyway. If it did append, 
it would be with the intention of that particular side-effect.

For 2, thanks to the const parameter, the function must copy the slice 
first before appending to it.

If the parameter is to a mutable slice (even with const elements) then 
the original slice can lose capacity. It is possible to come up with 
solutions that preserve the capacity of the original slice but I think 
the previous guideline is sufficient:

     foo(a.capacityPreserved);

(capacityPreserved can be a function, returning a RAII object, which 
calls assumeUnique in its destructor on the original slice.)

Does the guideline make sense?

Ali

[1] http://forum.dlang.org/thread/mhtu1k$2it8$1@digitalmars.com

[2] http://dlang.org/d-array-article.html


More information about the Digitalmars-d-learn mailing list