On Mon, Aug 27, 2018 at 06:11:14PM -0700, Walter Bright via Digitalmars-d wrote: > On 8/27/2018 10:08 AM, H. S. Teoh wrote: > > Const in D makes sense as-is. Though, granted, its infectiousness means > > its scope is actually very narrow, and as a result, we ironically > > can't use it in very many places, and so its touted benefits only > > rarely apply. :-( Which also means that it's taking up a lot of > > language design real estate with not many benefits to show for it. > > D const is of great utility if you're interested in functional > programming. Using it has forced me to rethink how I separate tasks > into functions, and the result is for the better. > > I agree that D const has little utility if you try to program in C++ > style.
I am very interested in functional programming, yet ironically, one of D's top functional programming selling points, ranged-based programming, interacts badly with const. Just ask Jonathan about using ranges with const, and you'll see what I mean. :-) The very design of ranges in D requires that the range be mutable. However, because const is infectious, this makes it a royal pain to use in practice. Take, for example, a user-defined container type, let's call it L. For argument's sake, let's say it's a linked list. And let's say the list elements are reference-counted -- we'll write that as RefCounted!Elem even though this argument isn't specific to the current Phobos implementation of RefCounted. As experience has shown in the past, it's usually a good idea to separate the container from the range that iterates over it, so an obvious API choice would be to define, say, an .opSlice method for L that returns a range over its elements. Now, logically speaking, iterating over L shouldn't modify it, so it would make sense that .opSlice should be const. So we have: struct L { private RefCounted!Elem head, tail; auto opSlice() const { ... } } The returned range, however, must be mutable, since otherwise you couldn't use .popFront to iterate over it (and correspondingly, Phobos isInputRange would evaluate to false). But here's the problem: because opSlice is declared const, that means `this` is also const, which means this.head and this.tail are also const. But since this.head is const, that means you couldn't do this: auto opSlice() const { struct Result { RefCounted!(const(Elem)) current; ... // rest of range API void popFront() { current = current.next; // Error: cannot assign const(RefCounted!Elem) to RefCounted!(const(Elem)) } } return Result(head); // <-- Error: cannot assign const(RefCounted!Elem) to RefCounted!(const(Elem)) } This would have worked had we used pointers instead, because the compiler knows that it's OK to assign const(Elem*) to const(Elem)*. However, in this case, the compiler has no way of knowing that it is safe to assign const(RefCounted!Elem) to RefCounted!(const(Elem)). Indeed, they are different types, and the language currently has no way of declaring the head-mutable construct required here. This is only the tip of the iceberg, of course. If you then try to add a method to RefCounted to make it convert const(RefCounted!T) to RefCounted!(const(T)), then you'll be led down a rabbit hole of further problems with const (e.g., how to implement ref-counting with const objects in a way that doesn't violate the type system) until you reach the point where it's impossible to proceed without casting away const somehow. Unfortunately, the spec says that's Undefined Behaviour. So you're on your own. This is just one example among many, that const is hard to use in the general case. It works fairly well for a narrow number of cases, such as for built-in types, but once you start generalizing your code, you'll find brick walls in undesired places, the workarounds for which require so much effort as to offset any benefits that const may have brought. TL;DR: const is beautiful in theory, but hard to use in practice. So hard that it's often not worth the trouble, despite the benefits that it undoubtedly does provide. P.S. If D had the concept of head-mutable, a lot of this pain (though not all) would have been alleviated. T -- "I'm running Windows '98." "Yes." "My computer isn't working now." "Yes, you already said that." -- User-Friendly