On Friday, November 17, 2017 07:40:35 Mike Parker via Digitalmars-d-learn wrote: > On Friday, 17 November 2017 at 03:15:12 UTC, Tony wrote: > > Thanks T! Good information, especially "iterating over a range > > is supposed to consume it". I have been reading > > dlang.org->Documentation->Language Reference, but should have > > also read dlang.org->Dlang-Tour->Ranges. Although that page > > You might also find use in this article (poorly adapted from > Chapter 6 of Learning D by the publisher, but still readable): > > https://www.packtpub.com/books/content/understanding-ranges > > > makes a distinction about "range consumption" with regard to a > > "reference type" or a "value type" and it isn't clear to me why > > there would be a difference. > > With a value type, you're consuming a copy of the original range, > so you can reuse it after. With a reference type, you're > consuming the original range and therefore can't reuse it.
Technically, per the range API, you can _never_ reuse a range. The only legitimate way to get a copy of a range to then iterate over separately is to call save on the range (which of course requires it to then be a forward range and not just an input range). However, unfortunately, for many common range types (dynamic arrays included), calling save and copying the range have the same semantics. So, it's easy to write code that will work with many ranges without calling save anywhere but falls flat on its face as soon as you use a range that actually requires that save be called (typically because it's either a reference type, or it's a pseudo-reference type where only some state gets copied when the range is copied, and you get particularly weird behaviors when reusing the range that was copied). So, while you can get away with reusing a range where save and copying the range do the same thing, it's an incredibly bad idea in general and definitely causes problems in generic code. Certainly, it should only be done when you're dealing with a specific range type where you know what the semantics of copying it are. In general, as soon as you've copied a range, it should never be used again unless it's assigned a new value. Of course, if something is truly an input range (and not a forward range that merely doesn't have save declared like it should), then it's particularly bad to be trying to use copies of ranges, because any range that can't be a forward range is by definition either a reference type or a pseudo-reference type where a copy is not fully distinct from the original (typically where making a copy isn't possible or where it would be too expensive to do so). Personally, I'm inclined to think that we should never have had save and should have required that reference type ranges which are forward ranges be wrapped in a struct where copying it does the same thing that save does now, but I seriously doubt that we could make a change that big now. And we'd still have to watch out for how input ranges are different, since copying them wouldn't and couldn't work the same (and while getting rid of save like that would really clean up some range stuff that uses forward ranges, it would make it a lot harder to distinguish between input and forward ranges). So, it's not like there would be a perfect solution even if we were redesigning things from scratch. Ultimately, folks just need to be aware that they need to be calling save when they want to actually copy a range and make sure that they unit test their code well to make sure that it works with various range types if it's generic code (and that it works with the exact ranges that it uses if it's not generic). Unfortunately, it's usually the case that when you first test range-based code with a range that doesn't implicitly save when it's copied that you find that your code doesn't work with it, because save wasn't explicitly called when it needed to be. But at least you then catch it and can fix your code. - Jonathan M Davis