D Programming Language 2.0

- Comments
- D
- Search
- Downloads
- Home

Last update Sun Oct 12 16:09:07 2008

# Const and Invariant

When examining a data structure or interface, it is very helpful to be able to easily tell which data can be expected to not change, which data might change, and who may change that data. This is done with the aid of the language typing system. Data can be marked as const or invariant, with the default being changeable (or mutable).

invariant applies to data that cannot change. Invariant data values, once constructed, remain the same for the duration of the program's execution. Invariant data can be placed in ROM (Read Only Memory) or in memory pages marked by the hardware as read only. Since invariant data does not change, it enables many opportunities for program optimization, and has applications in functional style programming.

const applies to data that cannot be changed by the const reference to that data. It may, however, be changed by another reference to that same data. Const finds applications in passing data through interfaces that promise not to modify them.

Both invariant and const are transitive, which means that any data reachable through an invariant reference is also invariant, and likewise for const.

## Invariant Storage Class

The simplest invariant declarations use it as a storage class. It can be used to declare manifest constants.

    invariant int x = 3;	// x is set to 3
    x = 4;			// error, x is invariant
    char[x] s;		// s is an array of 3 char's

The type can be inferred from the initializer:

    invariant y = 4;	// y is of type int
    y = 5;			// error, y is invariant

If the initializer is not present, the invariant can be initialized from the corresponding constructor:

    invariant int z;
    void test()
    {
        z = 3;		// error, z is invariant
    }
    static this()
    {
        z = 3;    // ok, can set invariant that doesn't have
                  // static initializer
    }

The initializer for a non-local invariant declaration must be evaluatable at compile time:

    int foo(int f) { return f * 3; }
    int i = 5;
    invariant x = 3 * 4;      // ok, 12
    invariant y = i + 1;      // error, cannot evaluate at compile time
    invariant z = foo(2) + 1; // ok, foo(2) can be evaluated at compile time, 7

The initializer for a non-static local invariant declaration is evaluated at run time:

    int foo(int f)
    {
      invariant x = f + 1;  // evaluated at run time
      x = 3;                // error, x is invariant
    }

Because invariant is transitive, data referred to by an invariant is also invariant:

    invariant char[] s = "foo";
    s[0] = 'a';  // error, s refers to invariant data
    s = "bar";   // error, s is invariant

Invariant declarations can appear as lvalues, i.e. they can have their address taken, and occupy storage.

## Const Storage Class

A const declaration is exactly like an invariant declaration, with the following differences:

- Any data referenced by the const declaration cannot be changed from the const declaration, but it might be changed by other references to the same data.
- The type of a const declaration is itself const.

## Invariant Type

Data that will never change its value can be typed as invariant. The invariant keyword can be used as a type constructor:

    invariant(char)[] s = "hello";

The invariant applies to the type within the following parentheses. So, while `s` can be assigned new values, the contents of `s[]` cannot be:

    s[0] = 'b';  // error, s[] is invariant
    s = null;    // ok, s itself is not invariant

Invariantness is transitive, meaning it applies to anything that can be referenced from the invariant type:

    invariant(char*)** p = ...;
    p = ...;        // ok, p is not invariant
    *p = ...;       // ok, *p is not invariant
    **p = ...;      // error, **p is invariant
    ***p = ...;     // error, ***p is invariant

Invariant used as a storage class is equivalent to using invariant as a type constructor for the entire type of a declaration:

    invariant int x = 3;   // x is typed as invariant(int)
    invariant(int) y = 3;  // y is invariant

## Creating Invariant Data

The first way is to use a literal that is already invariant, such as string literals. String literals are always invariant.

    auto s = "hello";   // s is invariant(char)[5]
    char[] p = "world"; // error, cannot implicitly convert invariant
    		    // to mutable

The second way is to cast data to invariant. When doing so, it is up to the programmer to ensure that no other mutable references to the same data exist.

    char[] s = ...;
    invariant(char)[] p = cast(invariant)s;     // undefined behavior
    invariant(char)[] p = cast(invariant)s.dup; // ok, unique reference

The `.idup` property is a convenient way to create an invariant copy of an array:

    auto p = s.idup;
    p[0] = ...;	  // error, p[] is invariant

## Removing Invariant With A Cast

The invariant type can be removed with a cast:

    invariant int* p = ...;
    int* q = cast(int*)p;

This does not mean, however, that one can change the data:

    *q = 3; // allowed by compiler, but result is undefined behavior

The ability to cast away invariant-correctness is necessary in some cases where the static typing is incorrect and not fixable, such as when referencing code in a library one cannot change. Casting is, as always, a blunt and effective instrument, and when using it to cast away invariant-correctness, one must assume the responsibility to ensure the invariantness of the data, as the compiler will no longer be able to statically do so.

## Invariant Member Functions

Invariant member functions are guaranteed that the object and anything referred to by the `this` reference is invariant. They are declared as:

    struct S
    {   int x;

        invariant void foo()
        {
    	x = 4;	    // error, x is invariant
    	this.x = 4; // error, x is invariant
        }

The const and invariant function attributes can also appear after the closing parenthesis of the parameter list:

    struct S
    {
        void bar() invariant
        {
        }
    }

## Const Type

Const types are like invariant types, except that const forms a read-only view of data. Other aliases to that same data may change it at any time.

## Const Member Functions

Const member functions are functions that are not allowed to change any part of the object through the member function's this reference.

## Implicit Conversions

Mutable and invariant types can be implicitly converted to const. Mutable types cannot be implicitly converted to invariant, and vice versa.

## Comparing D Invariant and Const with C++ Const

Const, Invariant Comparison
Feature | D | C++98
--- | --- | ---
const keyword | Yes | Yes
invariant keyword | Yes | No
const notation | Functional:
    //ptr to const ptr to const int
    const(int*)* p;
| Postfix:
    //ptr to const ptr to const int
    const int *const *p;
transitive const | Yes:
    //const ptr to const ptr to const int
    const int** p;
    **p = 3; // error
| No:
    // const ptr to ptr to int
    int** const p;
    **p = 3;    // ok
cast away const | Yes:
    // ptr to const int
    const(int)* p;
    int* q = cast(int*)p; // ok
| Yes:
    // ptr to const int
    const int* p;
    int* q = const_cast<int*>p; //ok
modification after casting away const | No:
    // ptr to const int
    const(int)* p;
    int* q = cast(int*)p;
    *q = 3;   // undefined behavior
| Yes:
    // ptr to const int
    const int* p;
    int* q = const_cast<int*>p;
    *q = 3;   // ok
overloading of top level const | Yes:
    void foo(int x);
    void foo(const int x);  //ok
| No:
    void foo(int x);
    void foo(const int x);  //error
aliasing of const with mutable | Yes:
    void foo(const int* x, int* y)
    {
       bar(*x); // bar(3)
       *y = 4;
       bar(*x); // bar(4)
    }
    ...
    int i = 3;
    foo(&i, &i);
| Yes:
    void foo(const int* x, int* y)
    {
       bar(*x); // bar(3)
       *y = 4;
       bar(*x); // bar(4)
    }
    ...
    int i = 3;
    foo(&i, &i);
aliasing of invariant with mutable | No:
    void foo(invariant int* x, int* y)
    {
       bar(*x); // bar(3)
       *y = 4;  // undefined behavior
       bar(*x); // bar(??)
    }
    ...
    int i = 3;
    foo(cast(invariant)&i, &i);
| No invariants
type of string literal | invariant(char)[] | const char*
implicit conversion of string literal to non-const | not allowed | allowed, but deprecated

Copyright © 1999-2008 by Digital Mars ®, All Rights Reserved | Page generated by Ddoc. | Comments