Bug with writeln?

Jonathan M Davis newsgroup.d at jmdavisprog.com
Thu Sep 6 20:05:56 UTC 2018


On Thursday, September 6, 2018 1:05:03 PM MDT Steven Schveighoffer via 
Digitalmars-d-learn wrote:
> On 9/6/18 2:52 PM, Jonathan M Davis wrote:
> > On Thursday, September 6, 2018 12:21:24 PM MDT Steven Schveighoffer via
> >
> > Digitalmars-d-learn wrote:
> >> On 9/6/18 12:55 PM, Jonathan M Davis wrote:
> >>> It's not a bug in writeln. Any time that a range is copied, you must
> >>> not
> >>> do _anything_ else with the original unless copying it is equivalent
> >>> to
> >>> calling save on it, because the semantics of copying a range are
> >>> unspecified. They vary wildly depending on the range type (e.g.
> >>> copying
> >>> a dynamic array is equivalent to calling save, but copying a class
> >>> reference is not). When you pass the range to writeln, you must
> >>> assumed
> >>> that it may have been consumed. And since you have range of ranges,
> >>> you
> >>> must assume that the ranges that are contained may have been consumed.
> >>> If you want to pass them to writeln and then do anything else with
> >>> them, then you'll need to call save on every range involved (which is
> >>> a
> >>> bit of a pain with a range of ranges, but it's necessary all the
> >>> same).
> >>
> >> This is not necessarily true. It depends how the sub-ranges are
> >> returned.
> >>
> >> The bug is that formattedWrite takes ranges sometimes by ref, sometimes
> >> not.
> >>
> >> formattedWrite should call save on a forward range whenever it makes a
> >> copy, and it doesn't.
> >>
> >> Case in point, it doesn't matter if you call writeln(b.save), the same
> >> thing happens.
> >
> > That's still not a bug in formattedWrite. save only duplicates the
> > outer-most range. And since writeln will ultimately iterate through the
> > inner ranges - which weren't saved - you end up with them being
> > consumed.
>
> That is the bug -- formattedWrite should save all the inner ranges
> (writeln calls formattedWrite, and lets it do all the work). To not do
> so leaves it open to problems such as consuming the sub ranges.
>
> I can't imagine that anyone would expect or desire the current behavior.

It's exactly what you're going to get in all cases if the ranges aren't
forward ranges, and it's what you have to do in general when passing ranges
of ranges to functions if you want to be able to continue to use any of the
ranges involved after passing them to the function. Changing formattedWrite
to work around it is only a workaround for this paricular case. It's still
a bug in general - though given that this would be one of the more common
cases, working around it in this particular case may be worth it. It's still
a workaround though and not something that can be relied on in with
range-based code in general - especially when most range-based code isn't
written to care about what the element types are and copies elements around
all the time.

> Ironically, when that bug is fixed, you *don't* have to call save on the
> outer range!

Except you do, because it's passed by value. If it's a dynamic array, then
you're fine, since copying saves, but in the general case, you still do.

> > When you're passing a range of ranges to a function, you need to
> > recursively save them if you don't want the inner ranges in the
> > original range to be consumed. Regardless of what formattedWrite does,
> > it's a general issue with any function that you pass a range of ranges.
> > It comes right back to the same issue of the semantics of copying
> > ranges being unspecified and that you therefore must always use save on
> > any ranges involved if you want to then use those ranges after having
> > passed them to a function or copy them doing anything else. It's that
> > much more annoying when you're dealing with a range of ranges rather
> > than a range of something else, but the issue is the same.
> It's only a problem if the subranges are returned by reference. If they
> aren't, then no save is required (because they are already copies). The
> fix in this case is to make a copy if possible (using save as expected).
>
> I think the save semantics have to be one of the worst designs in D.

On that we can definitely agree. I'm strongly of the opinion that it should
have been required that forward ranges be dynamic arrays or structs (no
classes allowed) and that it be required that they have a postblit / copy
constructor if the default copy wasn't equivalent to save. If you wanted a
class that was a forward range, you would then have to wrap it in a struct
with the appropriate postblit / copy constructor. That way, copying a
forward range would _always_ be saving it.

The harder question is what to then do with basic input ranges. Having them
share code with forward ranges is often useful but also frequently a
disaster, and to really be correct, they would need to either be full-on
reference types or always passed around by reference. Allowing partial
reference types is a total disaster when you're allowed to copy the range.
Requiring that they be classes would fix the problem nicely except that it
would then require heap allocation for all basic input ranges.

Basic input ranges and forward ranges are the same when all you're doing is
iterating over them, but as soon as you pass them around - even to a foreach
loop (since a foreach loop copie the range) - it gets hairy fast.
Semantically, basic input ranges really should be reference types, whereas
forward ranges really should be value types (or at least their outer layer
needs to act like a value type - deep copying would not be good, whereas the
behavior that dynamic arrays have would be fine).

So, I don't know exactly what I'd do if we could do this from scratch, but I
am quite sure that I would not have save be a thing. That really needs to
just be what happens when copying normally. Unfortunately, we can't exactly
fix that now. Ranges make up what is arguably our greatest set of idioms,
but they're also where we made some of our greatest mistakes. *sigh*

- Jonathan M Davis





More information about the Digitalmars-d-learn mailing list