i think it would be nice to have a way to semantically mark items as semver exempt instead of having to rely on users thoroughly reading documentation. there would be a warn by default lint for using items marked as semver exempt, with the warning message recommending pinning the exact version then allowing the lint.
the most common usecase would probably be for macro-internal functions that should only be called by generated code.
I've used that before in the typetoken crate, making a #[doc(hidden)]Sealed trait so that only macros could implement a marker trait, but people said it was misleading, so i removed it and just marked the trait as unsafe instead.
I was one of those people. I must admit I didn't really consider SemVer. But... after thinking about it for a minute now, I don't think it would change my suggestion. At least as far as naming goes.
#[doc(hidden)]
pub mod private_do_not_use {
//! do not use anything from this module, it will break the saftey
//! guarantees provided by this crate.
//!
//! these are only public so they can be used from macros.
pub unsafe trait Sealed {}
}
/// marker trait that a type is a type token.
pub trait Token: Sealed {}
The mod is pub so nothing is actually sealed, so it's not the case that only macros can implement the marker trait. And AIUI this is required or your macros couldn't implement the trait either, so making it actually sealed isn't an option.
Thus suggestion of making Token itself unsafe with a requirement of "you just can't, so don't" to achieve the same effect. (If actually sealing things was an option, I would have recommended that instead.)
If you think you might want to change the shape of trait in some way and want that to be SemVer compatible, I could see splitting the trait up. But then the supertrait still wouldn't be a seal, and would presumably be the core trait too.[1] So it'd be more like
This way the Sealed trait still shows up as a supertrait of Marker, so someone looking at Marker still sees that there's some bound that they aren't able to satisfy. If the bound is merely due to exemption from semver stability of implementation, then this is sufficient, but if there's some unsafe reliance on properties of the implementation, one or both of the traits should still be marked as unsafe to represent that. Consider that downstream is still "allowed" to implement the semver-exempt trait, they're just opting out of semver stability if they deliberately use an undocumented API surface.
Personally I like (ab)using the non_ascii_idents lint for this too (though very disappointed that requires globally allowing it rather than in a scope)
Aren't all those suggestions (they work and somehow get the meaning across) just a workaround for exactly this issue, without addressing it directly, without containing the actual meaning?
You have some piece of code that has to (for some reason) be public but shouldn't be used at all. Give it a weird or descriptive name and make it #[doc(hidden)] and write the reason in the comment. It works but I'd say isn't ideal.
You have code where you actually have to look into memory safety. Use unsafe. At the moment there is no way of indicating (besides a # Safety comment) what exactly is unsafe, thus I'd argue using unsafe for this purpose is misleading at best.
From those suggestions I like the doc(hidden)__semver_exempt best, but I think having an attribute specifically made for internal and/or semver_exempt would clearly get the meaning across. Maybe with internal automatically hiding it from the docs, while semver_exempt doesn't, since that's something you may want to give others access to (opt-in, similar to how some crates have an unstable feature flag.
Without thinking about it, without knowing much about Rust, you immediately know the meaning of it and that your code may break at any future release of that library if you use this, even without any linters.
really, this is still a workaround. with the way rust handles macro hygine, it should be possible to introduce a pub(macro) visibility, which makes path's only referencable from spans from the crate it was defined in. this should work nicely with declarative macros, but will require careful use when combined with procedural macros.
In fact, with the way that "macros 2.0" hygiene works, the expansion of a macro can actually refer to private names -- name resolution and privacy is entirely done with the span context of where the macro is written instead of from the context of the crate expanding the macro. Even ignoring the obvious differences, there are further subtle differences from current "semitransparent mixed site" hygiene (primarily w.r.t. the interaction between multiple macro expansion contexts) that make it difficult to fully adopt the "def site" hygiene as a stable possibility, though.
pin! initially relied on this to directly construct a Pin with struct literal syntax without unsoundly exposing the inner field, even. This was changed to use the stability system to avoid having stable functionality rely on unstable language capabilities to even be possible.