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