Type qualifiers – const and immutable

Both const and immutable are type qualifiers. This means that, when applied to any type, they actually create a new type. For example, the following equivalent declarations all create a type that is called const(int).

const int x = 10;
const(int) y = 11;
const z = 12;

In the declaration of z, the compiler will infer the type. For a basic type such as int, it makes no difference which syntax is used. However, we'll see shortly that things can be a bit confusing with derived data types, so you may want to get into the habit of using the syntax in the second line when you need to explicitly specify the type. In the rest of this section, we're going to explore the contracts of both const and immutable, then take a look at how they apply to the types we've seen so far.

The contracts

When you declare a variable as immutable, you are stating unequivocally that it is never, ever going to change throughout the lifetime of the program. This is a very strong contract and the compiler is going to run on the assumption that you really mean what you say. This allows the potential for optimizations that otherwise would not have been possible.

Anything declared as const is making a guarantee that no data will be modified through that particular reference. This is less strict than the contract of immutable, because it's still possible for the data to be modified through another, non-const reference.
Another property of const and immutable is that they are both transitive. This means that, if you apply them to a thing, then anything else that is reachable through that thing is also const or immutable. You'll get a basic sense of this in this chapter, but you'll achieve full clarity in the next chapter when you learn how to qualify user-defined types.

With the basic types

Applying const and immutable to one of the basic types is straightforward. Despite the difference in the contracts, there is effectively no difference between them in this case as the basic types are all value types. When one is assigned to another, the right operand is copied to the left operand. There's no way to change the original value through the new one.

immutable x = 10;
const y = 11;
const int z;
z = 12;   // Error

All three variables are completely protected from mutation beyond the point of declaration. x is forever 10, y is forever 11, and z is doomed to remain 0, the value of int.init.

With pointers

When applying const or immutable to a pointer declaration, you first need to decide if it should apply only to the data, or to the pointer as well.

const(int)* q;      // Mutable pointer to const data
const(int*) r;      // const pointer to const data
const int* s;       // const pointer to const data

Without parentheses, it's easy to forget which kind of pointer you have. Also, note that none of these guys are initialized; two of them are forever null. In the first declaration, q is a mutable pointer. It need not point at the same location forever. It can be assigned a new address at any time, but the data stored at that address can never be mutated. r and s are const pointers, yielding an error if you try to point them somewhere else.

int x = 10;
q = &x;     // OK: mutable pointer
r = &x;     // Error: const pointer
*q = 11;    // Error: const data

Where the contract of const comes into play is that it's possible mutate const data through a different pointer where const is not involved, as shown in this example:

const(int)* cp;
int* p;
int x = 10;
cp = &x;        // OK
p = &x;         // OK

There are two ways to modify x behind cp's back. First, you can assign a value to x directly. Second, you can do it through the pointer p. const pointers are often used as function parameters, where the function is promising it won't modify your data through the pointer during the function's lifetime.

The syntax for immutable is the same. The difference is in the contract. If you have a pointer to immutable data, then the data must be immutable through all other pointers to that data. The original data must also be immutable. Consider the following:

immutable(int)* ip;
int x = 10;
ip = &x;        // Error: x is not immutable
immutable int y = 11;
ip = &y;        // OK

You cannot assign ip a pointer to x because then there are no guarantees that x will never be mutated elsewhere in the program. It works for y because y is immutable data and cannot be modified directly. That said, you could always cast immutable away. Consider the following two lines:

immutable int y = 10;
immutable(int*) py = &y;

We know that it's an error to assign a new address to py because the pointer, not just the data, is declared as immutable (the * is inside the parentheses). Attempting to assign the address of a mutable int to py will result in a compiler error, but take a look at this:

writeln(py);
int** ppy = cast(int**)&py;
int j = 9;
*ppy = &j;
writeln(py);

This prints two different addresses, showing a successful violation of immutable's contract. Doing something like this could lead to corrupt data, a segfault, an access violation, or who knows what else. In fact, here's a demonstration of "who knows what else":

immutable int x = 10;
int* px = cast(int*)&x;
*px = 9;
writeln(x);

This snippet takes the same approach used to modify py above. Compiling and running this, though, actually prints 10. The compiler, assuming that an immutable int is never going to be modified, has changed writeln(x) to writeln(10). This is one of the optimizations enabled by immutable. Taken together, these two examples demonstrate why casting away immutable is never a good idea. It is undefined behavior and anything can happen.

The same can be said for const. It's not a violation of the const contract to modify source data through another reference, but it certainly is a violation to cast const away and modify it. There's more to the const story, but it's more clearly demonstrated in the context of user-defined types in the next chapter.

With arrays

The general idea with arrays is the same as with pointers. For example:

const(int)[] t;     // Mutable array, const data
const(int[]) u;     // const array, const data
const int[] v;      // const array, const data

Here, none of the metadata associated with u or v can be modified through u or v. Not the pointer, the length, the capacity, nothing. As with pointers, it's still possible for another, mutable slice to reference the same data. What about t?

t[0] = 1;      // Error: const data
t ~= [3,2,1];  // OK: mutable array
t.length = 30; // OK: mutable array

The first line fails as one would expect; you can't modify const data. The last two lines show what it means to have a mutable array. Tacking a slice onto the end of it does not violate the contract, since none of the original elements are mutated and they are all still reachable from their original indexes in the array, no matter that they may have been copied elsewhere during a reallocation. The only modification was to the array metadata. Ditto for the assignment to length. This all also holds true for immutable. I trust you can work out how its contract would apply if you swap it in for const in this example.

You can also apply const and immutable to associative arrays and strings. Recall that strings are declared as arrays with immutable data. If you want to further make the array metadata immutable or const, you can do it as with any other data type, such as immutable(string), which is effectively immutable(immutable(char)[]). This is exactly the same as immutable(char[]).

Conversions

Types that are passed around by value are implicitly convertible between immutable, const, and unqualified; immutable and unqualified are both implicitly convertible to const, but not the other way around.

immutable int x = 10;
int y = x;                  // OK: value types
immutable(int)* ipx = &x;
const(int)* cpx = ipx;      // OK: immutable to const
const(int)* cpy = &y;       // OK: unqualified to const
int* px = ipx;              // Error

You can think of const as a bridge between the three. This is particularly helpful with function parameters if the mutability of the source variable is irrelevant.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.98.148