1 /** 2 * Small utility library to ensure a reference is emplaed on the stack. 3 * 4 * The empty asm call is currently enough to force all current compilers to 5 * give up on trying to optimize the destructor out. If this becomes incorrect 6 * at some point, then a revisit is necessary. 7 * 8 * License: Boost-1.0. See LICENSE.MD 9 * 10 * Authors: Steven Schveighoffer 11 * 12 * Copyright: 2021 Steven Schveighoffer 13 */ 14 module keepalive; 15 16 /** 17 * Create a guaranteed-stack-allocated reference. While the thing itself cannot 18 * be copied, it should be usable just like the reference that it wraps. 19 * Because of the destructor, it is guaranteed to be put on the stack. 20 * 21 * Params: 22 * val: The value to wrap. It must be a reference type. 23 * Returns: 24 * A voldemort type that wraps the value. Using it should be no different 25 * than the original pointer, 26 */ 27 @nogc nothrow @safe pure 28 auto keepAlive(T)(T val) if (is(T == U*, U) || is(T == class) || is(T == interface)) 29 { 30 static struct KeepAlive 31 { 32 T _val; 33 alias _val this; 34 @disable this(this); 35 36 @nogc nothrow @safe pure ~this() { 37 version (GNU) asm @nogc nothrow @safe pure { "" :: "rm" (this._val); } 38 else asm @nogc nothrow @safe pure {} 39 } 40 } 41 return KeepAlive(val); 42 } 43 44 /// Note, it's not considered a hard failure for the first test to fail, as 45 /// it's within the compiler's right to place any pointer it wants onto the 46 /// stack. In the case that does happen, print a warning. 47 unittest 48 { 49 static class C { 50 static int dtorcalls = 0; 51 ~this() { ++dtorcalls; } 52 } 53 54 import core.memory; 55 56 auto c = new C; 57 foreach(i; 0 .. 100) GC.collect; 58 if(C.dtorcalls != 1) 59 { 60 import std.stdio; 61 writeln("WARNING: keepalive issue not detected"); 62 } 63 C.dtorcalls = 0; 64 65 auto c2 = keepAlive(new C); 66 foreach(i; 0 .. 100) GC.collect; 67 assert(C.dtorcalls == 0); 68 }