From D Wiki
Revision as of 14:20, 14 November 2015 by Schuetzm (talk | contribs) (Created page with "{| class="wikitable" !Title: !'''Layz Initialization of const Members''' |- |DIP: |85 |- |Version: |1 |- |Status: |Draft |- |Created: |2015-11-14 |- |Last Modified: |{{REVISIO...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search
Title: Layz Initialization of const Members
DIP: 85
Version: 1
Status: Draft
Created: 2015-11-14
Last Modified: 2015-11-14
Author: Marc Schütz


This DIP proposes an officially sanctioned way to initialize const members (or mutable members of const structs) lazily.


Lazy initialization is a widely applied technique to 1) defer initialization of a struct or class member until it is actually needed, and 2) cache and reuse the result of an expensive computation. With current const semantics in D, this require objects and structs containing such variables to be mutable. As a consequence, any method that wants to make use of a lazily calculated member cannot be annotated as const, and all functions calling these methods require non-const references to such objects in turn.


A new annotation of member variables is proposed, reusing the existing keyword lazy. A member annotated as lazy triggers the following behaviours:

  • it is required to be private
  • no immutable objects with a lazy member may be created
  • it can be assigned to even if it is const, but only if it has been read at most once

The first two rules are enforced statically. The last one is checked by an assert() at runtime using a hidden flag member; in -release mode, this check (including the hidden member) is removed.

The second rule is necessary because static immutable objects could be placed in physically read-only memory by the linker and therefore cannot be modified, while dynamically created immutable objects can be shared across threads and modifications would be prone to race conditions.

The last rule ensures that external code can never observe a change to the field's value.

The compiler needs to make sure not to apply optimizations based on the assumption that a lazy member never changes. Use of lazy in this way is therefore @safe. It is, however, limited to values that can be written to the member directly. More complex applications, e.g. memoization depending on parameters using an associative array, can be implemented by casting the const-ness away. The compiler will still make sure that no breaking optimization are applied, but it can no longer enforce correctness, which makes casting away const un-@safe.


import std.stdio;
class MyClass {
    void hello() { writeln("Hello, world!"); }
struct S {
    // reference types
    lazy private MyClass foo_;
    @safe @property foo() const {
            foo_ = new MyClass;
        return foo_;
    // value types
    lazy private int bar_;
    @safe @property bar() const {
            bar_ = expensiveComputation();
        return bar_;
    // complex
    lazy uint[uint] factorial_;
    @trusted factorial(uint x) const pure {
        if(factorial_ is null)
            factorial_ = createEmptyAA!(uint[uint]); // initialize once
        auto tmp = cast(uint[uint]) factorial_;      // cast to mutable
        if(auto found = x in tmp)
            return *found;
        auto result = computeFactorial(x);
        tmp[x] = result;
        return result;

void main() {
    const S s;
    writeln("bar = ", s.bar);
    writeln("5! = " s.factorial(5));
    writeln("6! = " s.factorial(6));


This document has been placed in the Public Domain.