Abstract
it'd be nice to be able to specify what exactly is deprecated as opposed to deprecating all instances of an item. It could be done by supplying what = "value"
where value could be:
-
impl
- implementation of this trait or trait method is deprecated but calling the method or using trait in bounds is fine -
call
- calling this trait method is deprecated but implementing it is fine -
bound
- using this trait in bounds is deprecated
Use cases
Error trait
std::error::Error
has cause
deprecated as it got replaced by source
However source
is not present in old version of std
. So a crate that intends to support old versions of Rust has to call cause
and get a warning in the new versions of Rust. (This is a real case in the slog
crate.)
This situation is not ideal because calling cause()
should be perfectly fine. It just forwards to source
. implementing cause()
is problematic without source()
and redundant if source
is present. Thus it'd make more sense to only warn when cause()
is being implemented, not when it's called.
Using #[deprecated(what = "impl")]
on cause()
would tell the compiler that implementing the method is deprecated but would not lead to warnings if cause()
is called.
From/Into
Since From
implies Into
, types implementing Into
are superset of types implementing From
As a result, it makes more sense to implement From
and bound by Into
or call into()
. These attributes could warn users about this being the case:
#[deprecated(what = "bound")]
trait From<T> {
// I think what = "bound" should imply deprecated what = "call" on each trait method
// But for illustration purposes I wrote it explicitly
#[deprecated(what = "call")]
fn from(val: T) -> Self;
}
#[deprecated(what = "impl")]
trait Into<T> {
fn into(self) -> T;
}
#[allow(deprecated)]
impl<T, U> Into<U> for T where U: From<T> {
fn into(self) -> T {
T::from(self)
}
}
This would remind people to do the right thing.
Low level and high-level methods
Consider a hypothetical trait:
trait Foo {
#[deprecated(description = "use high_level instead" what = "call")]
fn low_level(&mut self);
// #[deprecated(what = "impl")] intentionally not used because allowing specialization is useful
fn high_level(&mut self) {
// do some other logic
// #[allow(deprecated)] could be implicit in this specific case
self.low_level()
// perhaps more logic
}
}
This would help people understand the meaning of the methods better. One example of a trait like this is std::io::Read
however I'd not sugggest to add it to that one since raw read()
is still widely used. OTOH, maybe actually warning newbies about it is a good thing and slapping `#[allow(deprecated)] to mean "I know read is not guaranteed to read whole buffer" may be fine. That being said this debate is out of scope of my post. For now.
Helper marker traits
arse_arg
has two similar traits: ParseArg
and ParseArgFromStr
. ParseArgFromStr
requires FromStr
and implies ParseArg
. This API makes it possible to implement ParseArg
for PathBuf
(without causing issues with encoding) while not burdening implementors of ParseArg
too much if the type already implements FromStr
. A similar approach is used in embedded-hal
.
A downside of this is there's a risk that someone will misunderstand the purpose of those traits and bound by ParseArgFromStr
by accident. Adding #[deprecated(what = "bound")]
to ParseArgFromStr
would help mitigating this risk.
What do you think?
Is it worth the effort to do RFC for this? Do you see any problems with this idea?