In a fascinating example of Ideology Driven Development, this post on the D Language Forums recently blew up and brought attention to a fantastic example of why D needs to be a Corporate Language. While there are some fairly egregious examples of IDD in this thread, the primary message that D's release practices are bordering on chaotic is an important one as it highlights another area of the lack of corporate-style discipline that D could benefit from. As such, this article will attempt to summarize the current state of affairs in D, analyze the community-offered solutions, compare and contrast it with other competitive languages, and finally suggest a path forward and hopefully serve as a constructive jumping-off point for a pragmatic discussion, as opposed to the current ideological mess that is the present forum thread.
The State of Chaos
Perhaps the most succinct way to describe D's development and release process is: Brutally Utilitarian. Roughly once a month D does a "major" release and up to 3 point releases throughout the month. I believe the technical term for D's release model is "rolling release". Whatever is in master at release time is tagged and dumped onto the market. Deprecated code is removed in pretty much whichever major release somebody can get the PR approved. Have a partially implemented feature in master? Oh yea, that absolutely needs to get dumped on the public. Want to change the code-gen, go ahead, just pile it in there with everything else. Oh, you have a legitimate bug fix, yeap, we'll take all of those and shotgun them into the release along with everything else.
The net result is that no release is guaranteed to be "stable" relative to any prior release. In practice, most releases do not break backward compatibility significantly, at least with the more recent releases, but trying to build a library that hasn't been updated in a year can be a challenge. This inevitably leads to a situation where two dependencies require mutually exclusive versions of the compiler to build and development halts. Not a good look in a corporate setting.
LTS is Not the Answer
The chaos above has led to an outcry in the community to bring some sort of order to the chaos. Thus far the community appears to be coalescing around some sort of LTS model as its preferred solution, however, the pushback from the maintainers has primarily been around the complexity of maintaining an LTS compiler release. On this point the maintainers are correct. An LTS release model implies that there are multiple supported historical releases, similar to the Ubuntu model from which most engineers derive their experience with the LTS model.
The reality is that D simply lacks the headcount to achieve this lofty goal. Backporting bug fixes to multiple supported releases is a significant amount of work that is exponentially more costly the further back in time the fix has to go. Furthermore, this level of maintenance complexity is unnecessary and likely counter-productive to the long-term success of D. In the future such a model may be beneficial but under the present limitations, it is simply unworkable.
A Survey of Peers
However, if LTS is a bad idea, what might we do instead? Perhaps looking at what other similar language peers to D might provide a suitable answer. In the name of preventing this article from turning into a book, we'll limit this survey to three popular D competitor languages: C++, Java, and C#/.NET and the survey will not be exhaustive, if it's not important enough to be listed in the Wikipedia on the language it won't included here.
C++ uses a fairly straightforward release model. The language and standard library are updated every 3 years and no guarantee is provided that new library features will compile on older compilers. However, all language and library features from previous versions are guaranteed to work in the newest compiler version, thus ensuring that all prior written code compiles in with the latest version of the compiler.
C++ Release Model:
Language and Library features are released every 3 years.
Breaking changes are typically resolved by introducing a new feature.
Non-breaking bug fixes are at the vendor's discretion.
Deprecated language features are rare, and are not removed until at least the following major release, if not later.
While surveying I could not find any examples of non-security-related bug fixes being backported into prior releases from any of the major vendors (GCC, LLVM, MSVC).
The C++ release model is fairly sedate and is frequently criticized for its slow pace, however, that same slow pace allows for code to be rewritten well in advance of the feature being removed. C++ places a high premium on stability and backward compatibility so there is little need for the concept of LTS in C++, there are just different versions. Furthermore, the relative stability of the language means that in nearly all cases, code written 10 or more years ago compiles and executes on the latest compilers with no additional effort.
Java is the only surveyed example of a language that fully employs an LTS model. Throughout its history, Java the language has been highly coupled to Java the library, and as a result, it is possible to tightly couple the language and library in a single LTS release. Language feature deprecations are incredibly rare in Java. The first major language deprecation (finalizers) occurred in March of 2022, and as such, code written in Java 1 is virtually certain to compile in Java 21.
Java Release Model:
Language and Library features are released every 6 months.
Breaking changes are typically resolved by introducing a new library feature and obsoleting the old library feature or deprecating and removing the language feature.
Non-breaking bug fixes are at the vendor's discretion.
Deprecated language features are rare, and are not removed until some number of releases in the future.
As Java uses an LTS model, security-related bug fixes are backported to the prior releases.
Java utilizes a fairly rapid release cadence that is combined with high language stability. This release model practically demands an LTS model as the library is relatively fast moving and the stability of the language makes pushing out new features relatively easy. However, it is critically important to note that Java is backed by a massive corporation that provides all the necessary resources to maintain and improve the entire ecosystem. There are teams of people who are paid to do nothing but backport fixes into older but still supported releases.
C#/.NET employs a hybrid LTS model. The language itself does not use LTS, but the library does. Both the C# (and VB.NET) language and the .NET library are released one year cadence and new C# language features are utilized by the attached .NET library. The .NET Library receives an LTS release every other year, and only the most recent LTS is supported, the practical effect is that only two versions of .NET are supported at any given time.
C#/.NET Release Model:
Language and Library features are released once per year.
Breaking changes are typically resolved by introducing a new library feature and obsoleting the old library feature.
Compiler bugs are resolved in the following version.
There are no deprecated or removed from the language. Code written in C# 1.0 will compile with C# 12 (upcoming release).
Library fixes are not backported per se, but the current version of the standard library also supports the LTS version and thus fixes are naturally available to the LTS library.
Perhaps the most interesting characteristic of this release model, is that because nothing is ever removed from the language itself, it is possible to compile code that was built on the very first version of the compiler. However, it is important to note that because the compiler is tightly coupled with the standard library, it is not guaranteed that the latest compiler will generate code capable of running on older versions of the library.
C#/.NET utilizes a yearly release model which attempts to strike a balance between the glacial pace of C++ and the rapid cadence of Java. This cadence allows for ample time to test the new compiler and libraries, the beta campaigns will usually begin about 6 months before the scheduled release date. However, the C# and .NET teams routinely introduce new features in the first 2-3 months of the testing cycle, and while the justification for this is not obvious it does allow them to bring in larger features that may have taken more time to develop As with Java, C# is also backed by a massive corporation, but even so, they have adopted a less intensive release model that requires fewer resources from both the company and the community.
A Path Forward
Having analyzed a few different release models, we can begin to conceptualize a new release model for D that strikes a balance between the resource limitations and the community's need for improved stability. To that end, I believe that a combination of the C# and C++ models would appear to be the most likely fit.
One of the major complaints is that the language itself is an ever-moving target. To that end, the first Feature Release must be used to clean up the definition of language. If the maintainers want to remove something from the language (not the library) they would mark it as deprecated for the next normal monthly release. That release becomes the last monthly release. After that release, the yearly cycle would begin. This gives the community time to prepare for the removal of those features. Once the first yearly release is cut, the language is considered "stable". The process of adding new language features begins with the next feature release cycle. After the first yearly is cut, the removal of language features would involve a significant multi-year deprecation process.
Another idea here is that D may not be utilizing the capabilities of Git to its maximum potential. The model would work like this:
The 'master' branch is the current feature development branch.
New features are developed in their entirety in separate feature branches that are branched from 'master'.
Feature branches are not merged into 'master' until they are complete. The branch is left open until the yearly feature release is cut to facilitate feature-level bug fixes. Additionally, this can be used to facilitate using feature flags for experimental features included in the feature.
When a new yearly feature release is cut, the release is branched from 'master'.
Bug fixes are made against the 'master' branch in separate branches. Non-breaking bug fixes can also be merged into the latest release branch from the bug branch.
D Release Model:
Yearly Language+Library Feature release cycle.
New features are only made available in the current
Bug fixes are always rolled into 'master'.
Non-breaking bug fixes are rolled into the latest release branch, and optionally further back.
Bug fix releases on the latest release branch are made monthly unless no bugs are fixed that month.
6/3/3 Development Model.
6 months for feature development.
3 months of initial testing while still allowing features to be merged.
3 months of final testing, no features are added.
One of the benefits of this model is that it will simplify the process of integrating D with GCC and LLVM as those projects have a slower release cycle. Another benefit is that ample time is provided to the community to better test features before they are released. There is more flexibility for the community here as well, if the engineer wishes to use the latest prerelease version they are free to do so, they may choose to keep updated versions of the latest release branch version, or they may simply standardize on the latest release version and wait for the next release.
The end result is a pseudo-LTS model that keeps the maintance overhead reasonable for the maintainers while addressing the primary complaints of the community.