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