Package Opt-in and Default Stability
I agree the the default should be unstable, but I think that making packages opt out of this would be better. There aren’t a lot of libraries (22) which would need updates, and all of those libraries need to be recompiled for every release anyway. Could a flag be added to mojo package that makes everything stable by default, and that can be used for compatibility as libraries migrate? Adding a flag to each library in the mojo community repo + Modular’s stuff shouldn’t be super difficult, especially since as far as I know most libraries are quite unstable at the moment, with major API changes most releases, so unstable by default may better fit the current state of the ecosystem.
Compiler Flag: -warn-on-unstable-apis
I don’t think this is a good default. It would be very easy for someone not paying attention to Mojo development to stumble across a feature that does exactly what they want which is unstable, and which we later change due to soundness or API design issues. The next logical solution to this is a -no-warn-on-unstable-apis flag, but I don’t think that’s a good idea either. As Mojo evolves, different parts of the stdlib may be at different levels of unstable, from what we had with early reflection (compiler segfaults all around), to __type_of which, at least to my external observation, was more or less done from when it was introduced. I think that looking at Rust’s system of stability feature flags works out a lot better here, since someone who wishes to make use of an unstable API can opt into a single unstable feature-set without removing the warnings when they use other unstable APIs. This allows for people who want to write production code with unstable features that they require (for example, the Linux kernel and Rust’s allocation support) to ensure a very narrow scope of risk. For instance, this would allow Modular to determine when something is “stable enough” for use in MAX or other products without requiring additional linting or review to avoid highly unstable features slipping in to production code.
See The Unstable Book - The Rust Unstable Book and Stability attributes - Rust Compiler Development Guide for examples.
I think that links to tracking issues is particularly useful, since that can be used to point users towards a place where they can figure out if the feature is “stable enough” for their use-case. However, I’d like to expand this idea to the ecosystem instead of making the stdlib special, so a full link instead of just an issue number would be desirable. I also think that having the stable feature still have the associated feature flag is helpful because when a feature is marked as stable, the compiler can help with the move to stability by issuing warnings for everywhere that is explicitly marked as unstable with that feature.
Whether Mojo wants to adopt Rust’s policy of making nightly the only compiler that can access unstable features is another discussion. This is done via some compile-time configuration of the compiler and can be overriden via the bootstrap variable, but I can see arguments that people using unstable features should be updating fairly frequently, and others that auditing unstable features for soundness can be difficult so they would prefer to stay on stable releases instead of doing constant audits.
@stable(recursive=True)
I think that feature flags can help here as well. Presumably, when someone uses this kind of import they will audit the code under it. In order to avoid accidentally opting into things they weren’t expecting, having specific features given as part of this would ensure that users are warned of new unstable features which may not have been audited, and allows for enabling, say, an unstable data structure implementation without also dragging in its highly experimental yield-based iterator which has some outstanding compiler bugs.
Q: Should tests be able to use unstable APIs without warnings?
I think that making tests document which features they depend on would be helpful if they depend on unstable API surfaces, since this can be also be re-used to categorize tests by feature flag. For example, @test(feature="collections/dict") should be able to enable that feature in the test code and allow the user to run collections or collections/dict as a test category via some integrations between TestSuite and either environment variables or a future mojo test cli.
Versioning scheme. I did not put any constraints there, though we’re thinking about standard semantic versioning.
Would @stable(if: fn() -> Bool) work? I can think of a lot of things which could make some APIs unstable under some circumstances. For instance, there are plenty of MAX APIs which are only stable on NVIDIA or AMD CDNA, and I think we will find that some SIMD APIs only really work if AVX512, ARM SVE or RISC-V Vectors is present in the target and there may be a lag time before a good implementation for Scalar, SSE, AVX, AVX2 and NEON can come about. Of course, “compare with a constant we shoved in the package root somewhere” is easy to do and a function for that can be provided. I’ve seen Rust run into a few situations where stability was blocked because of difficulties on one target, when most other targets where perfectly fine, and I think that allowing library developers to express a more granular “stability” would be helpful. This could also neatly roll in feature flags if and make those library level constructs (aka a big pile of constants). It will generate some extra work for the compiler, so I’d be happy if @stable_if was broken out and moved to its own thing while @stable remained fairly restrictive but fast.
I think that avoiding strings for the simple reason of not needing to do big piles of string parsing as part of this would be helpful, so perhaps a SemVer(major, minor, bugfix) type would be helpful?