You should probably read this tutorial on ranges if you haven't.
When a range will and won't be consumed depends on its type. If it's an input range and not a forward range (e.g if it's an input stream of some kind - std.stdio.byLine
would be one example of this), then iterating over it in any way shape or form will consume it.
//Will consume
auto result = find(inRange, needle);
//Will consume
foreach(e; inRange) {}
If it's a forward range and it's a reference type, then it will be consumed whenever you iterate over it, but you can call save
to get a copy of it, and consuming the copy won't consume the original (nor will consuming the original consume the copy).
//Will consume
auto result = find(refRange, needle);
//Will consume
foreach(e; refRange) {}
//Won't consume
auto result = find(refRange.save, needle);
//Won't consume
foreach(e; refRange.save) {}
Where things get more interesting is forward ranges which are value types (or arrays). They act the same as any forward range with regards to save
, but they differ in that simply passing them to a function or using them in a foreach
implicitly save
s them.
//Won't consume
auto result = find(valRange, needle);
//Won't consume
foreach(e; valRange) {}
//Won't consume
auto result = find(valRange.save, needle);
//Won't consume
foreach(e; valRange.save) {}
So, if you're dealing with an input range which isn't a forward range, it will be consumed regardless. And if you're dealing with a forward range, you need to call save
if you want want to guarantee that it isn't consumed - otherwise whether it's consumed or not depends on its type.
With regards to ref
, if you declare a range-based function to take its argument by ref
, then it won't be copied, so it won't matter whether the range passed in is a reference type or not, but it does mean that you can't pass an rvalue, which would be really annoying, so you probably shouldn't use ref
on a range parameter unless you actually need it to always mutate the original (e.g. std.range.popFrontN
takes a ref
because it explicitly mutates the original rather than potentially operating on a copy).
As for calling range-based functions with forward ranges, value type ranges are most likely to work properly, since far too often, code is written and tested with value type ranges and isn't always properly tested with reference types. Unfortunately, this includes Phobos' functions (though that will be fixed; it just hasn't been properly tested for in all cases yet - if you run into any cases where a Phobos function doesn't work properly with a reference type forward range, please report it). So, reference type forward ranges don't always work as they should.
LockingTextReader(stdin)
as opposed to an array. One of them is single-pass, the other is multi-pass.) – MistookmyFunc(myArray[10..20])
there is no qualifier that would make it acceptable for the function to reassign the range on my end, so ref makes no sense. Even less so for non-array collections. – InterrelateArray
s, proxies for these, etc.) that I have to treat differently, after, say, aforeach
? – Mistookforeach
is defined in either case... it just behaves differently in each case! – Mistooktest2
above, for example, because you are placing no restrictions on the type R, you cannot guarantee thatF
will not modify its state. Ifitems
is a forward only file buffer, it may well be empty whenF
returns. If that's the case though, items will still be in a valid (empty) state in the callertest2
because the range will be a reference. Ifitems
is a dynamic array,F
might consume its range, without affectingtest2
. – Interrelateforeach
modifies a range or not. (Btw, const ranges never work. Ever. :-) ) – Mistookstd.range.isForwardRange
is the built in 'repeatable' range contract. – Interrelate