On Tuesday, 16 February 2016 at 06:04:42 UTC, Jonathan M Davis wrote:
On Monday, 15 February 2016 at 22:48:16 UTC, Walter Bright wrote:
rears its head again :-)

Head Const is what C++ has for const, i.e. it is not transitive, applies to one level only. D has transitive const.

In this particular case, doesn't this really just come down to mangling? It's undefined behavior in D to mutate a const object (even if it was constructed as mutable), so we can't just slap D const on C++ types and have that work, since the C++ code could legally mutate the object. But since C++ considers it defined behavior to mutate a const object by casting away const (or at least it does in all but some very specific cases), can we just get away with the D code treating D const as mutable? If so, then it's purely a matter of mangling. And for that, we could do something like add @cppconst or cppconst that the compiler recognizes and which is only valid in extern(C++) declarations. It would unfortunately have to be more than a simple attribute, because it would have to apply to parts of a parameter's type like C++'s const does (instead of the whole type at once), but if it were implemented such that it was just something that went on the extern(C++) function parameter types (or return types) for mangling purposes, and the D code just treated it as mutable, then it would at least restrict the muck to extern(C++).

Actually, one could argue that the fact that we support const at all with other languages - even with C - is broken in that C/C++ code can cast away our const and mutate the variable we passed in, breaking our type system. We rely on the programmer to know that the C/C++ code isn't going to do that, and most of the time, you really have no clue. You just assume that it won't - and it probably won't, but you don't know for sure.

So, most of the time, it will work just fine, but const with extern(C) and extern(C++) really does not provide the guarantees that const is supposed to provide.

Of course, there's also the issue that with extern(C) and extern(C++), everything is really __gshared, and yet we typically treat it like it's thread-local just like the D code is. Arguably, all parameters and return types with extern(C) and extern(C++) should be automatically treated as shared, and not doing so is risking a world of pain if the C/C++ code misbehaves, but having them be treated as shared would be really annoying to deal with, and most C/C++ code that's being called probably isn't doing anything with threads that could violate D's guarantees about thread-local variables (just like most C/C++ code presumably isn't casting away const and mutating when we pass it something from D) - but there's no guarantee that it isn't.

So, as far as compiler guarantees and the type system go, extern(C) and extern(C++) are a bit of a disaster, much as they generally work in practice. But I don't know what we can really do about that other than trying to educate people about the risks and possibly have the compiler avoid certain types of optimizations around extern(C) and extern(C++) code (which it may already do).

- Jonathan M Davis

Reply via email to