const(FAQ)
D's const/immutable system is unique, and so there are a lot of questions about it.
- Why does D have const?
- What principles drove the D const design?
- What is transitive const?
- What is head const?
- What is tail const?
- What is logical const?
- Why not use readonly to mean read only view?
- Why did Java reject const?
- How does const differ in C++?
- Why are strings immutable?
- Why aren't function parameters const by default?
- Are static class members covered by transitive const?
- What is immutable good for?
Why does D have const?
People often express frustration with const and immutable in D 2.0 and wonder if it is worth it.
- It makes function interfaces more self-documenting. Without transitive const, for all pointer/reference parameters one must rely on the documentation (which may be missing, out of date, or wrong). Note that without transitivity, C++ const is nearly useless for such self-documentation, which is why C++ programmers tend to rely on convention instead.
- It makes for interfaces that can be relied upon, which becomes increasingly important as more people become involved with the code. In other words, it scales very well. People working on projects with large teams of programmers say that lack of const makes their lives difficult because they cannot rely on the compiler to enforce convention. The larger the team, the worse it gets. Managing APIs is critical to a large project - it's why BASIC doesn't scale (as an extreme example).
- Const transitivity makes for some interesting optimization opportunities. The value of this has not been explored or exploited.
- Here's the biggie. Points 1..3 are insignificant in comparison. The future of programming will be multicore, multithreaded. Languages that make it easy to program them will supplant languages that don't. Transitive const is key to bringing D into this paradigm. The surge in use of Haskell and Erlang is evidence of this coming trend (the killer feature of those languages is they make it easy to do multiprogramming). C++ cannot be retrofitted to supporting multiprogramming in a manner that makes it accessible. D isn't there yet, but it will be, and transitive const will be absolutely fundamental to making it work.
Of course, for writing single-threaded one developer programs of fairly modest size, const is not particularly useful. And in D const can be effectively ignored by just not using it. The only place const is imposed is with the immutable string type.
What principles drove the D const design?
- It must be mathematically sound. That means there are no legal escapes from it.
- Any type can be wrapped in a struct and the resulting struct can still exhibit the same const behavior - in other words, no magic behavior for certain types.
- Const behavior must be transitive.
- Const behavior for type T must be equivalent for all types T.
What is transitive const?
Transitive const means that once const is applied to a type, it applies recursively to every sub-component of that type. Hence:
const(int*)** p; p += 1; // ok, p is mutable *p += 1; // ok, *p is mutable **p += 1; // error, **p is const ***p += 1; // error, ***p is const
With transitivity, there is no way to have a const pointer to mutable int.
C++ const is not transitive.
What is head const?
Head const is where the const applies only to the component of the type adjacent to the const. For example:
headconst(int**) p;
would be read as p being a: const pointer to mutable pointer to mutable int. D does not have head const (the headconst is there just for illustrative purposes), but C++ const is a head const system.
What is tail const?
Tail const is the complement of head const - everything reachable from the const type is also const except for the top level. For example:
tailconst(int**) p;
would be read as p being a: mutable pointer to const pointer to const int. Head const combined with tail const yields transitive const. D doesn't have tailconst (the keyword is there just for illustrative purposes) as a distinct type constructor.
What is logical const?
Logical const refers to data that appears to be constant to an observer, but is not actually const. An example would be an object that does lazy evaluation:
struct Foo { mutable int len; mutable bool len_done; const char* str; int length() { if (!len_done) { len = strlen(str); len_done = true; } return len; } this(char* str) { this.str = str; } } const Foo f = Foo("hello"); bar(f.length);
The example evaluates f.len only if it is needed. Foo is logically const, because to the observer of the object its return values never change after construction. The mutable qualifier says that even if an instance of Foo is const, those fields can still change. While C++ supports the notion of logical const, D does not, and D does not have a mutable qualifier.
The problem with logical const is that const is no longer transitive. Not being transitive means there is the potential for threading race conditions, and there is no way to determine if an opaque const type has mutable members or not.
Reference: mutable: bitwise vs. logical const
Why not use readonly to mean read only view?
Readonly has a well established meaning in software to mean ROM, or Read Only Memory that can never be changed. For computers with hardware protection for memory pages, readonly also means that the memory contents cannot be altered. Using readonly in D to mean a read only view of memory that could be altered by another alias or thread runs counter to this.
Why did Java reject const?
https://bugs.java.com/bugdatabase/view_bug?bug_id=4211070
How does const differ in C++?
C++ has a const system that is closer to D's than any other language, but it still has huge differences:
- const is not transitive
- no immutables
- const objects can have mutable members
- const can be legally cast away and the data modified
- const T and T are not always distinct types
Why are strings immutable?
Why aren't function parameters const by default?
Since most (nearly all?) function parameters will not be modified, it would seem to make sense to make them all const by default, and one would have to specifically mark as mutable those that would be changed. The problems with this are:
- It would be a huge break from past D practice, and practice in C, C++, Java, C#, etc.
- It would require a new keyword, say mutable.
- And worst, it would make declarations inconsistent:
void foo(int* p) { int* q; ... }
p points to const, and q points to mutable. This kind of inconsistency leads to all sorts of mistakes. It also makes it very hard to write generic code that deals with types.
Using in can mitigate the ugliness of having to annotate with const:
void str_replace(in char[] haystack, in char[] needle);
Are static class members covered by transitive const?
A static class member is part of the global state of a program, not part of the state of an object. Thus, a class having a mutable static member does not violate the transitive constness of an object of that class.
What is immutable good for?
Immutable data, once initialized, is never changed. This has many uses:
- Access to immutable data need not be synchronized when multiple threads read it.
- Data races, tearing, sequential consistency, and cache consistency are all non-issues when working with immutable data.
- When doing a deep copy of a data structure, the immutable portions need not be copied.
- Invariance allows a large chunk of data to be treated as a value type even if it is passed around by reference (strings are the most common case of this).
- Immutable types provide more self-documenting information to the programmer.
- Immutable data can be placed in hardware protected read-only memory, or even in ROMs.
- If immutable data does change, it is a sure sign of a memory corruption bug, and it is possible to automatically check for such data integrity.
- Immutable types provide for many program optimization opportunities.
const acts as a bridge between the mutable and immutable worlds, so a single function can be used to accept both types of arguments.