Initializing an Immutable Field with Magic: The "Fake Placement New" Technique

FeepingCreature feepingcreature at gmail.com
Fri Jul 26 10:11:10 UTC 2019


How would you initialize an immutable field outside the 
constructor?

For instance, assume you're trying to implement a tagged union, 
and you want to switch it to a new type - but it so happens that 
the type you're trying to switch to is an immutable struct.

...
immutable struct S { int i; }
union
{
   ...
   S s;
}

For instance, you might, like me, decide that std.conv.emplace 
does what you want:

...
   emplace(&s, S(5));
...

You would then get a strange compiler error that the return type 
of a "side effect free" function cannot be silently thrown out; 
and if you changed the call to, as DMD recommends, `cast(void) 
emplace(&s, S(5));`, you would discover with some astonishment 
that the call is silently removed.

Emplace does not emplace!

What's happening here? From the perspective of the compiler, it 
makes perfect sense.

Emplace is defined as a pure function, meaning that it cannot 
have any effects other than on its parameters. However, its 
parameters are, in order, an immutable struct ("can't" change the 
caller - it's immutable) and a value parameter, S, which also 
can't change the caller. And we told DMD to throw away the return 
value.

So DMD ends up, rather reasonably, convinced that this emplace is 
a no-op. We would need to convince DMD of something like, "it 
looks like I'm giving it an S*, but it's actually a ~magical 
type~ with the same layout as S but not immutable". This is not 
possible.

Instead, we have to use the same magic hack that emplace also 
uses internally: fake placement new!

See, there is *exactly one* construct in the language that is 
allowed to assign a new value to an immutable field, and it's the 
constructor. So we have to make DMD believe that our variable is 
a field in a type we control (hack 1), and then explicitly call 
that type's constructor with our new value (hack 2).

struct Wrapper
{
   S s;

   this(S s)
   {
     this.s = s; // the one operation allowed to assign immutable 
fields
   }
}

`Wrapper` has the same layout as `S`, because it basically *is* 
`S`.

Then we call the constructor as if we were currently constructing 
Wrapper at a location that "just happens" to overlap with our 
field.

Wrapper* wrapper = cast(Wrapper*) &s;
wrapper.__ctor(S(5)); // fake placement new

What a mess. Works though.

Demo: https://run.dlang.io/is/kg7j3f


More information about the Digitalmars-d mailing list