D Editions

Quirin Schroll qs.il.paperinik at gmail.com
Wed Jun 12 09:46:49 UTC 2024


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.


More information about the dip.ideas mailing list