Article: Why Const Sucks

H. S. Teoh hsteoh at quickfur.ath.cx
Mon Mar 5 17:38:52 UTC 2018


On Mon, Mar 05, 2018 at 03:57:35AM -0700, Jonathan M Davis via Digitalmars-d-announce wrote:
> Here's something I wrote up on const:
> 
> http://jmdavisprog.com/articles/why-const-sucks.html
> 
> I suppose that it's not exactly the most positive article, but I feel
> that it's accurate.
[...]

Yeah, I found myself in the same boat recently.  As you often say, const
and ranges simply don't mix.  And modern idiomatic D is 90% about
ranges, so that alone instantly reduces the scope of const's usefulness
by a lot.

A case in point: I was implementing a container recently, and wrote an
opSlice() method to return a range over its elements. My initial thought
was that it could be made const, since the range wouldn't mutate the
underlying elements, but only represent a mutable slice over a const
container, i.e., the slice can mutate, but the elements cannot, just
like iterating over string (== immutable(char)[]). Should be easy,
right?

	struct Container {
		auto opSlice() const {
			static struct Result {
				private Container impl;
				private int n; // internal mutable state
				@property bool empty() { ... }
				... // rest of range API
			}
			return Result(this);
		}
	}

Well, that didn't work, because in opSlice, `this` is const, and I can't
initialize Result.impl which is a mutable Container. Well, no biggie,
just make it const:

	struct Container {
		auto opSlice() const {
			static struct Result {
				private const(Container) impl;
				private int n; // internal mutable state
				@property bool empty() { ... }
				... // rest of range API
			}
			return Result(this);
		}
	}

At first, this worked, and it seems that I could have my const cake and
eat it too.  Until I decided at some point that I needed to make it a
forward range, which requires a .save method. So I tried:

	...
	static struct Result {
		private const(Container) impl;
		...
		@property Result save() {
			Result copy = this;
			return this;
		}
	}

That seemed to do the trick, everything compiles and works.  But I soon
ran into an unfixable problem: the resulting range, while it works in
the simplest cases, started causing mysterious compile errors when I
tried to use it with Phobos range algorithms. Eventually, I discovered
that the underlying problem was that Result, as defined above, was a
struct with a const member, and therefore it was illegal to assign it to
a variable of the same type outside of initialization (since doing do
meant you were overwriting a const field with something else, which
violates the constness of the field).  This broke the by-value
assumption inherent in much of Phobos code, so the resulting range ended
being unusable with most Phobos algorithms.  Which defeated the whole
purpose in the first place.

While I'm sure with enough time and patience Phobos could be fixed to
support this kind of range, the decision I was faced with was: should I
(1) persist in using const, and thereby stall my project while I work on
huge chunks of Phobos range algorithms to make them usable with ranges
that have const members (not to mention spending how much time waiting
in the Phobos PR queue and potentially getting things rejected because
it might cause breakage of unknown amounts of existing code), or (2)
just remove `const` from my code, and be able to continue with my
project *right now*?  The choice was a no-brainer, sad to say.

So yeah, while D's const provides actual guarantees unlike C++'s
laughable const-by-documentation, that also limits its scope so much
that in practice, it's rarely ever used outside of built-in types like
string. Which also limits the usefulness of its guarantees so much that
it's questionable whether it's actually worth the effort.


T

-- 
It won't be covered in the book. The source code has to be useful for something, after all. -- Larry Wall


More information about the Digitalmars-d-announce mailing list