Type qualifiers are modifiers applied to data types. The most useful one
in D is the immutable qualifier,

immutable(T)

which creates a new type from type T that cannot be written to, and is guaranteed
to never change. The immutable type qualifier is transitive, meaning any data reachable
through an instance of type immutable(T) is itself immutable. In the vernacular this
is called transitivity, or more simply, it's “turtles all the way down”.

Immutable types are particularly useful when doing multithreaded programming, as
no synchronization is necessary for immutable types.

When using immutable qualifiers in the real world, however, one runs into some unexpected
issues. For example, given a simple function to add a constant to an indirect value:

int func(int* p) {
return *p + 7;
}

What happens if we try to pass it an immutable(int)* argument
(which is a pointer to an immutable int)? It'll fail to compile
as a pointer to immutable cannot be implicitly cast to a pointer to a mutable (if this was
possible, there'd be no useful static typechecking of immutability). Our hapless programmer
is reduced to writing the function twice:

int func(immutable(int)* p) {
return *p + 7;
}

Meanwhile, he curses the designer of the language as he is forced to commit the cardinal
sin of copypasta'ing his code. To add further insult, the compiler generates exactly the
same native code for each of those two functions.

This miserable state of affairs is addressed by adding a new type qualifier: const.
A const type cannot be written to, but also it is not guaranteed the data is immutable.
The data could be changed via another mutable reference to it.
Like immutable, const is transitive.

The beauty is that both T and immutable(T) can be implicitly cast to const(T), so only
one version of the function needs to be written:

int func(const(int)* p) {
return *p + 7;
}

and everyone is happy, the angels sing, but hmm, there still seems to be an evil stench
here somewhere. What about this, the identity function:

int* func(int* p) {
return p;
}

Throwing in a const:

const(int)* func(const(int)* p) {
return p;
}

Hmm, that doesn't really work. An identity function should return the same type that
it was given, not that type converted to const. What about using templates? We can write:

T func(T)(T p) {
return p;
}

which will solve the problem, won't it? T can take on int*, const(int)*, and immutable(int)*.
Sure, for that function and similar ones. But template functions:

can't be used for virtual functions

can't be used for a binary interface

will generate multiple copies of a function that are the same except for the type
signature (arguably, this should be fixed by better compiler technology)

But worse, consider:

struct S {
T p;
}
T* func(S)(S s) {
return &s.p;
}

or even some fiendishly complex compound type built out of T. We want the type qualifier of
S transferred to T. If a const or immutable S is passed, then the code won't compile, as the
return type is T*, not const(T)* or immutable(T)*.
Attempting to enhance the template system to create a ‘type qualifier
parameter’
consumed many gallons of coffee and many hours at the coffee shop. All the ideas
were rejected as just too painfully ugly. It just was a bad fit for templates, or we just
weren't smart enough. A new approach was needed.

We found it by simply coming up with a new type qualifier. I know, I know, too many type qualifiers
will sink the boat. Adding them is a dangerous game.

It looks like:

inout(T)* func(inout(S) s) {
return &s.p;
}

It isn't a template, and inout can only be used on function parameters and the return value.
inout is set to the type qualifier of its corresponding argument when it is called, and the
return type is constructed out of that qualifier. Inside the function, inout behaves similarly
to const. Voila, one function in source code and
in generated code. The argument s can be immutable, const or neither. Let's try it out
with a program that prints the type of the return value of foo():

which is what it must be. Furthermore, inside the function, immutable, const and unqualified
types cannot be implicitly cast to inout, and inout can only be implicitly cast to const.
This plugs any ‘leaks’
where immutable data could get infiltrated by mutable references.

Conclusion

Having an immutable type qualifier allows for static type checking of many desirable
qualities in data. But to have both mutable and immutable typed data in a program,
there needs to be mechanisms where single functions can be used for both. const
provides this for function parameters, and inout provides it for return values.
Both have an aura of inevitability about them, once that string gets pulled, the behavior
they must have can be derived.

Acknowledgements

Thanks to Kenji Hara and Steve Schveighoffer for their helpful comments on this article.