[phobos] I think we need to make an emergency release
Steve Schveighoffer
schveiguy at yahoo.com
Wed Aug 25 14:13:01 PDT 2010
----- Original Message ----
> From: Andrei Alexandrescu <andrei at erdani.com>
> To: Discuss the phobos library for D <phobos at puremagic.com>
> Sent: Wed, August 25, 2010 4:47:35 PM
> Subject: Re: [phobos] I think we need to make an emergency release
>
> On 8/25/10 12:48 PDT, Steve Schveighoffer wrote:
> > One very very notable difference from the original appender is it does not
>take
> > a pointer as the parameter. This means it does not update the original
>array
> > you pass to it (well, it does, but will not modify the length).
>
> I don't think this is good. When you format stuff you do want to append
> the formatted result incrementally to the string.
All it does, and I've made these changes in a couple places, is change this:
auto app = appender(&buffer);
...
return buffer;
to this:
auto app = appender(buffer);
...
return app.data;
You say it's needed, but in reality I didn't find a single compelling use case
in phobos, and Appender is used all over phobos. Most cases looked like this:
string buffer;
auto app = appender(&buffer);
and no reference to buffer later. Those were trivially replaced with this:
auto app = appender!string();
There was one case in stdio.d where appender is used to convert a utf-16 file to
utf-8, but appender already has code to take care of that (not added by me).
The new code looks less confusing to me, you be the judge:
[steves at steveslaptop phobos]$ svn diff std/stdio.d
Index: std/stdio.d
===================================================================
--- std/stdio.d (revision 1925)
+++ std/stdio.d (working copy)
@@ -2099,8 +2099,8 @@
* Read them and convert to chars.
*/
static assert(wchar_t.sizeof == 2);
- auto app = appender(&buf);
- buf.length = 0;
+ auto app = appender(buf);
+ app.clear();
for (int c = void; (c = FGETWC(fp)) != -1; )
{
if ((c & ~0x7F) == 0)
@@ -2120,11 +2120,13 @@
}
c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00);
}
- std.utf.encode(buf, c);
+ //std.utf.encode(buf, c);
+ app.put(cast(dchar)c);
}
}
if (ferror(fps))
StdioException();
+ buf = app.data;
return buf.length;
}
@@ -2138,20 +2140,16 @@
* cases.
*/
L1:
- if(buf.ptr is null)
- {
- sz = 128;
- auto p = cast(char*) GC.malloc(sz, GC.BlkAttr.NO_SCAN);
- buf = p[0 .. 0];
- } else {
- buf.length = 0;
- }
+ auto app = appender(buf);
+ app.clear();
+ if(app.capacity == 0)
+ app.reserve(128); // get at least 128 bytes available
- auto app = appender(&buf);
int c;
while((c = FGETC(fp)) != -1) {
app.put(cast(char) c);
- if(buf[$ - 1] == terminator) {
+ if(c == terminator) {
+ buf = app.data;
return buf.length;
}
===========================
I also suspect that the way this function's return value is typed is based on
the 'take-an-array-pointer' mode of Appender. I think it'd be better typed this
way:
private char[] readlnImpl(FILE* fps, char[] buf = null, dchar terminator = '\n')
That would get rid of the "set buf before returning" part of the code, you just
return app.data.
There were several cases where someone did something like this:
buffer.length = 0;
auto app = appender(&buffer);
or
auto app = appender(&buffer);
buffer.length = 0;
This is trivially replaced with this:
auto app = appender(buffer);
app.clear();
This tells appender, you can own and replace the data in buffer. It will not
append outside of buffer, rather it will realloc as necessary.
>
> > Other than that, here is the updated API (with private data shown for an
>idea of
> > how the implementation works):
> >
> > struct Appender(A : T[], T)
> > {
> > private struct Data
> > {
> > size_t capacity;
> > Unqual!(T)[] arr;
> > }
> >
> > private Data* _data;
>
> I think this looks good - why not make arr of type pointer to array and
> support append to existing arrays?
Safety concerns. You are most likely passing a reference to a stack-based array
reference, so if you escape the Appender, you escape the stack-based reference.
This isn't strictly necessary because the most important thing is the data.
Now, you could escape stack data by passing a reference to a stack-allocated
buffer, but I think there's no way around this.
The thing is, when requiring passing a pointer, you are *guaranteeing* the
chance for escaping. Even when you don't care about the original reference.
The fact that about half of phobos usages of appender looked like this:
string buffer;
auto app = appender(&buffer); // appender contains a pointer to stack data now!
tells me that people will make this mistake.
> > /**
> > Construct an appender with a given array. Note that this does not copy the
> > data, but appending to the array will copy the data, since Appender does
not
> > know if this data has valid data residing after it. The initial capacity
>will
> > be
> > arr.length.
> > */
> > this(T[] arr);
>
> Appender was partly created to avoid the issue the comment warns about.
All it means is, don't expect what you do to the appender to be reflected in the
original array reference. I think designing to support this is a huge safety
problem, and provides almost no useful benefit. If you have the appender, you
can get the appender's data, why do you have to use the original reference?.
Also note any aliases to the original array reference won't be updated.
-Steve
More information about the phobos
mailing list