Idea: Making API commitments explicit

A ton of t-lang design decisions hinge on thinking about "how can this code evolve", "what am I promising my users by writing this code", etc. And a lot of feature bikeshedding is about choosing sane defaults so crate authors know what they're committing to. I'm thinking we could make this a more explicit part of the language by letting crate authors more explicitly opt-in/opt-out to future changes.

My idea was a future_proof attribute that constrains what is or isn't allowed as a non-semver-breaking change:

  • An enum with #[future_proof(allow(add_variants))] is the same as a #[non_exhaustive] enum.
  • An enum with #[future_proof(forbid(add_variants))] is a normal enum.
  • An async method could be #[future_proof(implements(Send))].
  • A struct could be #[future_proof(covariant('a))].
  • A trait with #[future_proof(forbid(add_impl))] is effectively sealed.
  • A trait could be #[future_proof(dyn_safe)].
  • Some appropriate future_proof on a trait could relax or strengthen coherence rules.
  • etc

Idk if all these examples make sense, but you get the gist: crate authors have to think about semver-compatibility, but it's not always obvious what they're committing to or how to commit to something else. We usually provide future-compatibility knobs with various attributes or impls or things. Having a single attribute would make all these commitments discoverable in a single place. We could recommend using this attribute in api-guidelines, and provide typical choices for typical kinds of crates.

Does that make sense? Any other killer examples? Has something like this been proposed before? Even without the attribute, I think there's something to be said about making future-commitments a more first-class citizen in the language.

5 Likes

One way I've seen something like this come up previously was as something like #[non_exhaustive(but_all_the_fields_are_public)] or #[non_exhaustive(but_new_things_will_have_defaults)] or similar -- placeholder names, obviously -- to change it from "well you can change that type in any way" to "the changes are restricted in some ways to enable other functionality.

For example, but_all_the_fields_are_public would enable using FRU and but_new_things_will_have_defaults would allow constructing it with Foo { .. }.

5 Likes

Something like this recently came up in warn less about non-exhaustive in ffi by workingjubilee · Pull Request #116863 · rust-lang/rust · GitHub.

Another subtle case: pub struct Foo { pub x: u32, pub empty: ! } is considered uninhabited (and match ... {} will eventually be allowed on it. If you make empty private then suddenly Foo is considered nonempty by downstream users. Hell of a subtle point, I'd recommend an explicit #[future_proof(uninhabited)] or something

This is already a breaking change, I wouldn’t worry about it being more breaking when the type is uninhabited.

2 Likes

oh, yeah :person_facepalming:

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.