Must ranges support `r1 = r2;`?

Jonathan M Davis newsgroup.d at jmdavisprog.com
Sat Mar 24 23:02:38 UTC 2018


On Saturday, March 24, 2018 22:44:35 ag0aep6g via Digitalmars-d wrote:
> Long version: <https://issues.dlang.org/show_bug.cgi?id=18657>
> ("std.range and std.algorithm can't handle refRange").
>
> Short version: With two `std.range.RefRange`s, `r1 = r2;` does not what
> other Phobos code expects.
>
> Question is: Who's at fault? Where do I fix this? Do ranges have to
> support assignment as expected - even though std.range doesn't mention
> it? Or should range-handling code never do that - even though it comes
> naturally and is widespread currently?

I'll have to sit down and study RefRange again to really say whether it's
violating how ranges are supposed to work, but the key idea behind RefRange
is that it makes it possible to pass a range to a function and not have it
be copied in the process so that if you have something like

void foo(R)(R range)
{
    ...
}

you can pass it a range and continue to use what's left of the range after
it's done. As far as copying and assignment go, when you have

auto range2 = range1;

as far as generic code is concerned, it's effectively undefined behavior to
then do anything with range1. The same goes with passing a range to a
function by value or doing anything else that copies a range. The behavior
of how ranges in general work is not defined for copying.

If the range is a reference type, then range1 and range2 refer to each
other, and attempting to iterate either range1 or range2 would iterate the
other.

If the range is a value type, then range1 and range2 are then independent,
and iterating one does not affect the other.

If the range is a pseudo-reference type, then what happens to one when
trying to iterate the other depends entirely on the implementation. In the
case of dynamic arrays, you get the same behavior as a value type, but in
many other cases, you get a weird halfway state, because calling popFront on
range2 affects _some_ of the state in range1 but not all.

As such, in generic code, the only operation that I would expect to be valid
on range1 after

auto range2 = range1;

would be to assign range1 another value - be that range2 or another range of
the same type, at which point, range1 should then be able to be iterated
while the range that was assigned to it can't be, because it then would have
been copied, and the behavior for using it would be undefined. e.g.

auto range2 = range1; // now, range1 can't be used until it's assigned to
range2.popFront();

range1 = range2; // now, range2 can't be used until it's assigned to
range1.popFront();

And I don't think that RefRange violates that. Regardless, any generic,
range-based code that uses a range after copying it (and I mean actually
copying it, not calling save) is buggy code, and it _can't_ work correctly
because of how diverse the behavior of copying ranges is. Any generic, code
that copies a range and then uses it is wrong. Non-generic, range-based code
can do it, because the type of the range is well-known and thus the
semantics of copying are well-known and can be depended on, but taht's not
the case for generic, range-based code (which most of the range-based code
in Phobos is).

As far as save goes, save is supposed to create an independent copy to
iterate, and RefRange's documentation says that that's what it does, meaning
that passing a RefRange to a function that uses the forward range API
probably isn't very useful, but it would do what a forward range is supposed
to do. Whether the actual implementation does that properly on not, I don't
know - I'd have to study it - but per the documentation, it's at least
designed to do the right thing with save.

- Jonathan M Davis



More information about the Digitalmars-d mailing list