Input ranges are supposed to work like that when use as output
range. You could change that by also defining a put member for
them, but I don't think doing that would be a good idea. The
current behavior allows you to fill an input range using a
function that takes an output range. For example you can do
the following with an array:
auto a = [4, 3, 2, 1, 0];
copy([0,1,2], a);
writeln(a); // prints [0, 1, 2, 1, 0]
If an array had a put member that would append to it, this
would print [4, 3, 2, 1, 0, 0, 1, 2]. I think it is better
to have to explicitly state that you want to append to an
array. It should be possible to use RefAppender for that:
copy([0, 1, 2], appender(&a)); // error
This currently doesn't work (I think that because RefAppender
implements
put through opDispatch and constraint for range.put checks for
put member
using hasMember). You could write your own function to work
around that:
auto output(T)(ref T[] arr)
{
static struct R
{
T[]* arr;
void put(int v){ arr ~= v; }
}
return R(&arr);
}
auto a = [4, 3, 2, 1, 0];
copy([0,1,2], output(a));
writeln(a); // prints [4, 3, 2, 1, 0, 0, 1, 2]

OK, here is why:
put(R, v) has three modus operandi:
1. R is an input range with an lvalue front().
2. R is a function/delegate that accepts a v
3. R implements the method put(v)
slices fit into category one. In this case, guess what happens?
R.front = v;
R.popFront();
Why? Think of R as a *buffer* that is *pre-allocated* and needs to be
filled. This is what put is trying to do.
What you want is std.array.Appender, which defines the method put, which
*appends* data to the end instead of overwriting it.

This is just phobos being its usual nasty self claiming that char[] is not
an array of char, but actually a range of dchar. If I only had a nickel
every time someone ran into this "feature"... I'd probably have about $5
by now ;) It's one of the worst designs of Phobos.
-Steve