On Thursday, 30 May 2024 at 18:31:48 UTC, Atila Neves wrote:
> https://github.com/atilaneves/DIPs/blob/editions/editions.md
One hurdle I can see coming is the following: Writing and maintaining a compiler that just supports multiple editions is error-prone to begin with. A much, much bigger hurdle is writing and maintaining a compiler that supports all interactions between the semantics of different editions. If done as proposed, any compiler supporting 3 or more editions is doomed:
If n is the number of editions to support, the number of semantic interactions is n!/(n−2). For n = 2, that’s just 2 (one interaction forth, and another back). For n = 3, it’s already 6 and for n = 4, it’s 12. The way of handling deprecations is a lot like n = 2, as the compiler must somehow support changes in semantics, at least recognize an erroneous old way and diagnose it properly.
One might assume that the number of interactions would be n choose 2, which would render n = 3 (close to) feasible, but that disregards the difference between back and forth interactions. If functions in modules A
and B
of different editions call each other, it makes a difference if A.f
calls B.g
or the other way around: The lexically same signature of A.f
might mean something entirely different were it the signature of B.g
. (That is the whole point of having editions.)
For three editions X, Y, and Z, semantics could be that X → Z is defined through X → Y and Y → Z (and the other way around for Z → X), but without proof, my suspicion is that this cannot be done in general.
The question how older edition code calls newer edition code: Even in the ideal case, there are inheritance, delegates/function pointers and templates. My best guess is that inheritance and delegates/function pointers with some effort are largely doable. There’s the issue what storage classes and attributes mean exactly, that must be clearly defined. The biggest issue here is that it might not be possible without surprising the programmer. My fear is that templates will become worse than C++ templates. It’s already not that easy to reason about them. (Example: In D, one can’t instantiate any template due to auto ref
parameters – and that despite the fact that D officially has no function templates, it just has templates, and IFTI is defined for a template that happens to contain just a function declaration.) Don’t get me started on mixin templates, those are already next to impossible to write in a way that makes them impossible to use incorrectly.
In the not-so-ideal case, there’s a code base and some modules are older and for edition X. Then edition Y came out and newer modules were written for Y – which works as Y and X interactions are well-defined. The some X modules needed fixing and end up calling Y module functions because it’s just practical. Rinse and repeat with edition Z.
If we have to allow interactions between editions, do so by a narrowly defined subset of the language. Essentially, if a module A
is for edition X, and module B
for edition Y, for B
, declarations in A
that aren’t in that subset are effectively private, and vice versa.
Interactions between editions is something that even C++ does not do (editions are called language versions, but it’s conceptually the same). Using conditional compilation, you can write files that are compile with C++98 and C++23, but you can’t compile a.cpp with -std=c++98
and b.cpp with -std=cpp23
and expect that you can link a.obj and b.obj. The headers a.hpp and b.hpp, which both .cpp files include, are likely different depending on language version, but even assuming they’re not, even mangling can be different, what stdlib classes/functions do is different, etc., etc. What you could do, however, is use extern "C"
declarations.
Back to the common subset for D. From a conservative standpoint, let’s just allow extern(C)
non-template stuff. Effectively, extern(C)
would be the public
-across-editions visibility. The reason is simple: Whatever newer editions do, what extern(C)
declarations mean won’t change much. It’s not a panacea either, as extern(C)
declarations can carry attributes and their meaning can change. However, there’s no question what the parameter storage class in
means on an extern(C)
function, it’s just not allowed. Classes are out, too. Non-POD structs are out as well. Start from a very narrow subset, then expand as needed. For compatibility of editions to work with ≥ 3 editions, the subset must be very, very stable.