Skip to content

Instantly share code, notes, and snippets.

@JesseKPhillips
Created November 30, 2010 02:54
Show Gist options
  • Save JesseKPhillips/721066 to your computer and use it in GitHub Desktop.
Save JesseKPhillips/721066 to your computer and use it in GitHub Desktop.
Creating Safe Mutable members in a const/immutable object.
/**
* This code comes from a D newsgroup discussion by Max Samukha
* and Simen kjaeraas: http://thread.gmane.org/gmane.comp.lang.d.general/43408/focus=43476
*
* The intent is to provide a safe way to mutate some data in a const method.
*
* The rules to achieve this are:
* * Only mutable data can be assigned to a Mutable
* * Referenced fields can be modified (inner class fields, pointer target)
* * Value types can be modified if the encapsulating class
* is not declared const/immutable
*/
module lconst;
import std.string;
import std.traits;
@trusted:
/**
* Mutable encapsulates an unqualified type allowing its referenced item to
* be safely modified within a const function or immutable class.
*
* Mutable may not be safe when passing immutable class to other threads
* or across the network.
*
* The other question is if a mutable inner class would ever be allocated in
* read-only memory when instantiating an immutable outer class.
*/
struct Mutable( T ) if ( is( T : Unqual!T ) ) {
private T _payload;
this( T t ) {
_payload = t;
}
@trusted @property ref T get( )( ) const {
T* p = cast( T* )&_payload;
return *p;
}
alias get this;
@trusted ref opAssign( U : T )( auto ref U u ) {
T* p = cast( T* )&_payload;
return *p = u;
}
bool opEquals( )( ref const Mutable other ) const {
return _payload == other._payload;
}
bool opEquals( U )( auto ref U other ) const if ( is( U : T ) || is( T
: U ) ) {
return _payload == other;
}
string toString() {
return format("%s", _payload);
}
}
unittest {
struct Matrix
{
double determinant() const
{
if ( *m_dirty )
{
*m_determinant = 646.363; /* expensive calculation */;
*m_dirty = false;
}
return *m_determinant;
}
void set(int i, int j, double x) { *m_dirty = true; }
Mutable!(bool*) m_dirty;
Mutable!(double*) m_determinant;
};
}
unittest {
class Window
{
@safe void drawRectangle(int x, int y, int width, int height)
{
}
}
class C
{
int x, y, width, height;
Mutable!Window location;
this()
{
location = new Window;
}
@safe void draw() const { location.drawRectangle(x, y, width, height); }
}
const C c = new C;
c.draw();
}
unittest {
struct S {
int n;
}
struct foo {
int n;
foo* p;
Mutable!(S*) s;
Mutable!(int*) m;
void bar( ) const {
static assert(__traits(compiles, *m = *m + 1)); // works, m is newlevel
static assert(!__traits(compiles, n = n + 1)); // compile-time error, n is const for newlevel this
static assert(__traits(compiles, s.n = 3)); // Works, s.n is newlevel
static assert(!__traits(compiles, p.n = 4)); // compile-time error, p is newlevel, p.n is const for
// newlevel this
static assert(__traits(compiles, *p.m = *p.m + 1)); // Works, p.m is newlevel
}
}
void qux( const foo f ) {}
void quux( ref foo f ) { f.n++; }
const foo f;
f.bar( ); // compile-time error, f is const, cannot call newlevel
// functions on it
qux( f ); // compile-time error, const(foo) cannot be implicitly
// cast to newlevel(foo)
//quux( f ); // compile-time error, newlevel(foo) cannot be implicitly
// cast to foo
}
unittest {
@safe void bar( int n ) {}
class Inner { int n; }
class A {
Mutable!(int*) n;
Mutable!(int) n2;
Mutable!(Inner) innerClass;
// Would innerClass be placed into read-only memory
// when instantiated with: new immutable(A)?
this() { n = new int; innerClass = new Inner; }
void foo( int num ) {
n2 = num;
}
@safe void bar( int num ) const {
innerClass.n = num;
}
}
auto aImmu = new immutable(A);
auto aMu = new A;
int i = 8;
auto immuInner = new immutable(Inner);
static assert(!__traits(compiles, *aMu.n = &i),
"Assign pointer to int");
static assert(!__traits(compiles, aMu.n = i),
"Assign int to pointer");
static assert(__traits(compiles, *aImmu.n = i),
"Assign value to immutable object pointer");
static assert(__traits(compiles, aImmu.bar(6)),
"Call function modifying Mutable Class");
static assert(!__traits(compiles, aImmu.n = &i),
"Assign pointer to immutable object");
static assert(!__traits(compiles, aImmu.n2 = i),
"Assign value to immutable object");
static assert(!__traits(compiles, aImmu.foo(6)),
"Call function modifying Mutable value");
static assert(!__traits(compiles, aImmu.innerClass = new Inner()),
"Assign class to Mutable value");
static assert(__traits(compiles, *aMu.n = i),
"Assign value to mutable object pointer");
static assert(__traits(compiles, aMu.n = &i),
"Assign pointer to mutable object");
static assert(__traits(compiles, aMu.n2 = i),
"Assign value to mutable object");
static assert(__traits(compiles, aMu.bar(6)),
"Call function modifying Mutable Class");
static assert(__traits(compiles, aMu.foo(6)),
"Call function modifying Mutable value");
static assert(__traits(compiles, aMu.innerClass = new Inner()),
"Assign class to Mutable value");
static assert(!__traits(compiles, aMu.innerClass = immuInner),
"Assign immutable class to Mutable value");
//FIXME: Fails, is this do to an alias this bug?
static assert(__traits(compiles, bar(a.n)),
"Call function that takes an int");
// Other Items that should work when alias this is fixed
static assert(__traits(compiles, aMu.n2++),
"Increment int");
static assert(__traits(compiles, aMu.n2 += 7),
"opOpAssign");
aImmu.bar(6);
assert(aImmu.innerClass.n == 6);
}
unittest {
auto badClass = q{ class A {
Mutable!(int) n;
void bar( int num ) const {
n2 = num;
}
} };
static assert(!__traits(compiles, mixin(badClass)),
"Const function modify Mutable value");
}
@JesseKPhillips
Copy link
Author

One limitation is that value types declared as Mutable (Mutable!(int)) can not be changed if the encapsulating class is declared immutable. For this reason a Mutable value can not be changed inside a const function. I think this is acceptable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment