I found a way using associated constants to cause a custom compiler error whenever an implementation is used (which can be suppressed by requesting a crate feature then). I don't think I will use it, but want to share it nonetheless:
pub trait Tr {
const _DEPRECATE: () = ();
fn repr(&self) -> &[u8];
}
struct NoValue;
impl Tr for NoValue {
fn repr(&self) -> &[u8] {
b""
}
}
trait DeprecateTr {}
impl DeprecateTr for () {}
impl<T: ?Sized + DeprecateTr> Tr for T {
const _DEPRECATE: () = {
#[cfg(not(feature = "compat"))]
panic!("Implementation of Tr for () is deprecated. Enable feature 'compat' to suppress this error.");
};
fn repr(&self) -> &[u8] {
let _ = Self::_DEPRECATE;
b"\x00"
}
}
fn main() {
assert_eq!(NoValue.repr(), b"");
// The following line gives a custom compile time error
// unless the `compat` feature is used:
//assert_eq!(().repr(), b"\x00");
}
Or a simpler version:
const fn impl_deprecated() {
#[cfg(not(feature = "compat"))]
panic!("Implementation of Tr for () is deprecated. Enable feature 'compat' to suppress this error.");
}
impl Tr for () {
fn repr(&self) -> &[u8] {
let _ = impl_deprecated();
b"\x00"
}
}
Not sure, however, if it's guaranteed that the constant doesn't get evaluated unless the trait implementation is used. But I feel like it's too hackery anyway.