| |
| Posted by FeepingCreature | PermalinkReply |
|
FeepingCreature
| // So I was working on Nullable,
// and I thought: how could this be made to work better?
// The following code runs on DMD master,
// and it is, technically, `safe`;
// but it REALLY REALLY shouldn't be.
module turducken;
import std.algorithm;
import std.datetime;
// The Turducken Type Technique is the answer to a challenge.
// How do we perform late binding and early unbinding
// on a type that's really, really evil?
// First of all, let's set the stage.
// We're going to construct the Type from Hell.
// It's a struct value type, giving us the
// greatest number of avenues to be horrible.
struct Satan
{
// It has an immutable value field, referencing immutable data.
immutable int[] i;
// It has a SysTime: it's caused issues for
// me in the past and I hold a grudge.
SysTime st;
// It has no default constructor.
@disable this();
// It has an .init that violates its invariant.
bool properlyInitialized = false;
invariant { assert(properlyInitialized); }
// It has a copy assignment overload,
// specifically so we can forbid copy assignment.
void opAssign(Satan) { assert(false); }
// To confirm that every constructor call matches one destructor call,
// it counts references.
int *refs;
this(int* refs)
pure @safe @nogc
{
this.properlyInitialized = true;
this.refs = refs;
(*refs)++;
}
this(this)
pure @safe @nogc
{ (*refs)++; }
// Since a destructor is defined, we will definitely
// assert out if the .init value is ever destructed.
~this()
pure @safe @nogc
in(refs)
out(; *refs >= 0)
do { (*refs)--; }
}
// Now, this is the challenge.
// In a function that is
pure // pure,
@safe // safe,
@nogc // and nogc:
unittest
{
// perform late binding and early unbinding of the Type from Hell.
// Let's do some prep.
// (for validation)
int refs;
// (cheat a bit)
int* refsp = () @trusted { return &refs; }();
{
// We start with a default initialized variable.
Turducken!Satan magic;
// we bind it to a constructed value
magic.bind(Satan(refsp));
// There's now exactly one copy of Satan in the world:
assert(refs == 1);
// Just for laughs, bind over it:
magic.bind(Satan(refsp));
// Still only one copy around.
assert(refs == 1);
// all is well with the contained value
assert(magic.value.properlyInitialized);
// Now we unbind it before scope end
magic.unbind;
// Satan is gone now.
assert(refs == 0);
}
// And he was destroyed exactly once, as is proper.
assert(refs == 0);
}
// How did we do this?
// Turducken!
struct Turducken(T)
{
// The delicious center.
alias Chicken = T;
// Union ensures that the T destructor is not called.
union Duck
{
Chicken chicken; // chicken chicken? chicken!
}
// Struct ensures that moveEmplace and its magic
// constness "tolerant" (violating) memcpy can be used.
struct Turkey
{
Duck duck;
}
Turkey store = Turkey.init; // take you to the turkey store
bool set = false;
// Stick a straw in it to get at the delicious gooey center.
@property ref T value() in(set) { return store.duck.chicken; }
// And the special sauce:
void bind(T value)
{
// clean up in case we're back for seconds
unbind;
// Make a destructor-protected copy to stick in our store
Turkey wrapper = Turkey(Duck(value));
// Plow over the existing data in total disregard of constness
() @trusted { moveEmplace(wrapper, store); }();
set = true;
}
// Various condiments:
void unbind()
{
if (set)
{
static if (is(T == struct)) {
destroy(value);
}
set = false;
}
}
// Since we have shielded our value from D's watchful eyes,
// we have to remember to clean up manually
// Clean your dishes!
~this()
{
unbind;
}
}
// And that's the Turducken Type Technique. Deliciously evil!
|