std.format and uninitialized elements in CTFE

Steven Schveighoffer schveiguy at gmail.com
Sun Dec 8 20:18:23 UTC 2019


On 12/8/19 2:41 PM, berni44 wrote:
> As I wrote above, I was looking into issue 19769. This is the test given 
> there:
> 
> ```
> import std.uni;
> import std.format;
> static immutable x = format("%s", cast(const)"test".asCapitalized);
> ```
> 
> This doesn't compile, because asCapitalized returns a struct 
> (ToCapitalizerImpl), which contains uninitialized data (dchar[3] buf = 
> void;) and format tries to print that. It first tries to print it as a 
> range, but isInputRange misses somehow, that this struct is a range 
> (probably because of the cast(const), but I don't know exactly) and 
> therefore formatValueImpl tries to print the members of that struct 
> directly, but as buf is not initialized, that fails. And the result is a 
> strange error message (about uninitialized variables, where everything 
> looks initialized).


It definitely is the cast(const). Removing it works. And it makes sense 
-- a const type cannot be a range, as popFront can never be const.

So one thing you CAN do, in cases like this is just initialize the damn 
void stuff in CTFE. i.e. here: 
https://github.com/dlang/phobos/blob/a24888e533adfe8d141eb598be22a50df5e26a66/std/uni.d#L9390

change to:

auto result = ToCapitalizerImpl(str);
if(__ctfe) result.buf[] = dchar.init;
return result;

> 
> This bug could be fixed by making isInputRange recognize the struct as a 
> range. But while I looked, what happens, I found out, that printing 
> structs with uninitialized data fails (but with an errormessage, that 
> says, that this data is uninitialized). I thought, it would be nice, if 
> format would just print <void> instead of throwing an error and hence my 
> question if doing so is possible.

OK, I see what you mean. Confusing error messages can be indeed a huge 
problem.

But let's consider the fix I outline above. In this case, instead of 
"Test", like he expects, he's going to get x equal to (yes, I did it at 
runtime to see what it would be):

const(ToCapitalizerImpl)(const(Result)(const(ByCodeUnitImpl)("test"), 
4294967295), const(ToCaserImpl)(const(Result)(const(ByCodeUnitImpl)(""), 
4294967295), 0, "\0\0\0"), false, "\0\0\0", 0)

Which is going to result in a *different* confusion. Even if it said 
"<void>" like you want, it's still a pile of gibberish that's far from 
what you expect.

So which is better? A compiler error saying essentially in a long and 
confusing way, "dude, you really don't want to do that", or a resulting 
binary that has, um... that other thing instead of "Test"?

-Steve


More information about the Digitalmars-d mailing list