On Thursday, 18 February 2016 at 10:47:57 UTC, Timon Gehr wrote:
On 18.02.2016 10:24, Walter Bright wrote:
On 2/17/2016 11:58 PM, Timon Gehr wrote:
const(headconst(T)) is the same as const(T), no?

Er, yes?

Jonathan wanted to embed a mutable reference count within a const object. Contrary to your suggestion, headconst won't help with that.

Exactly. The problem that folks frequently want to be able to solve that they simply cannot solve with D's const (and headconst wouldn't help) is that they want to be able to pass an object to a function that takes it as const or return a const object from a const member function and have stuff like reference counting, caching, mutexes, etc. work - stuff that has to be part of the object (at least in pure code) but which isn't part of its logical state and cannot be const. That's why people keep asking for logical const. D's const simply does not work with many use cases (especially in the more performance-driven, @system cases), which tends to mean that you just can't use it. Generic code in particular can't afford to use it, because it immediately shuts out a whole set of types (just like marking a templated function with @safe or pure is a bad idea). So, const ends up being useful with built-in stuff like integers and arrays, but it generally fails once user-defined types get involved.

And remember that headconst is essentially cppconst, and C++ has to have the mutable keyword and allow casting away const and mutating in order to solve these sorts of problems. From what I can tell, fundamentally, there are some common use cases that will not work with const unless it has backdoors. Mutexes and reference counts are great examples of that. They need to be associated with the object, but they cannot be treated as const in order to work even if the actual data is treated as const. Aside from C++ interoperability, headconst really doesn't seem like it's going to solve much.

I love the fact that D treats it as undefined behavior to cast away const and mutate. That's a huge win with regards to compiler guarantees and being able to trust const not to mutate stuff on its own (though other, mutable references to the same data obviously can). And it's required for const to interact well with immutable. But having no backdoors at all keeps popping up as a problem - and one that headconst clearly can't solve, because it doesn't solve it in C++. Andrei has run into problems with this as he's being working on the new containers and RCString. What he needed was the mutable keyword, and he was casting away const and mutating to do it, and I had to point out to him that that wasn't defined behavior. And if he's forgetting that, what's the lay user doing? Some things need backdoors from const, or they simply can't use const, and if you use those in your code, it quickly cascades such that you're not using const much of anywhere, because you can't. A number of folks have stated in the newsgroup that they generally avoid const in D, because it's too restrictive to be useful.

We can decide that we just don't care about those problems and that you simply don't get to use const in those cases, but that means that you lose out on all of the benefits that you get with const preventing you from mutating objects that aren't supposed to be mutated, and if you have to strip out const from enough of your code base, then you lose out on its benefits entirely, meaning that C++'s const with its backdoors would have been a win in comparison, sad as that may be.

The more I look at it, the more I'm inclined to think that introducing @mutable for member variables with a corresponding, required attribute on the struct or class it's in (e.g. @has_mutable) is really what we need to be able to solve this problem and make D usable in some of these high performance cases that would be using the mutable keyword in C++. It solves the logical const problem without totally throwing away the compiler guarantees. Any type without @has_mutable functions as it always has, and the cases where @mutable/@has_mutable would be required would then work with const, gaining all of its benefits for the non-@mutable members, and it would allow the compiler to prevent you from doing stupid stuff like mutating immutable objects, because an object with @has_mutable couldn't be immutable.

Regardless, I really don't think that adding headconst is worth the complication. It doesn't solve enough to be worth it. I'd much rather see something specific to extern(C++) that's used for mangling and which doesn't allow you to pass const objects to it unless the C++ declaration is equivalent to transitive const (and probably which treats return types as transitive const if they're partially const). Remember that we ditched having headconst, tailconst, etc. in D ages ago, because it was deemed overcomplicated. And I don't think that that's changed. Personally, I think that it's too complicated in C++, with declarations like const T*, T const*, T* const, etc. Most non-experts are going to fall flat on their face trying to decipher C++ declarations with const in them once you start doing much beyond const T or const T*. Let's please not go anywhere near there with D.

- Jonathan M Davis

Reply via email to