Deprecated on re-exports
Deprecated is an attribute you can put on items to mark them as, well, deprecated. Any use of that item will give a warning, telling you that you're using something that's deprecated. The thing is, it doesn't always work...
Press for table of Contents
The deprecated attribute
Let’s start with some examples that work. Here, foo is clearly deprecated:
1#[deprecated]2fn foo() {}3
4fn main() {5 foo();6 //~^ WARNING use of deprecated function `foo`7}The attribute is slightly more flexible; You can also give a version and a reason like so:
1#[deprecated(2 since = "5.2.0",3 note = "foo was rarely used. Users should instead use bar"4)]5fn foo() {}Or, just a reason:
1#[deprecated = "foo was rarely used. Users should instead use bar"]2fn foo() {}Alright, makes sense.
Now, the reference says one more thing about the attribute:
When applied to an item containing other items, such as a module or implementation, all child items inherit the deprecation attribute.
Let’s take a look:
1#[deprecated]2mod a {3 pub struct Foo;4}5
6fn main() {7 let _x = a::Foo;8 //~^ ERROR use of deprecated unit struct `a::Foo`9}The struct Foo is still deprecated, while the attribute only applies to a.
And this doesn’t just work for functions or structs.
pretty much any kind of item works.
I say prety much, since use items are not supported.
However, if they were that’d be pretty useful.
Just look at this example:
1fn foo() {}2
3#[deprecated]4use foo as bar;5
6fn main() {7 bar();8}You might expect this to mean thattThe use of the name bar is deprecated, but foo isn’t.
But what does the rust compiler say?
Make your best guess now!
Turns out: it doesn’t work
Believe it or not, the answer is: absolutely nothing.
Nothing is deprecated, but the compiler is absolutely fine with the attribute being there.
The reason is that after name resolution, the use of bar doesn’t pass through the re-export anymore.
It just resolves directly to foo.
So, the fact that the use item is deprecated isn’t noticed.
WaffleLapkin were planning to fix that.
It seems reasonable that this should work, or otherwise at least warn that it doesn’t do anything.
What happened is that we found multiple other bugs with #[deprecated].
So, it’s quiz time. Based on what you read sofar, and what you maybe already knew. How many deprecation warnings does this code produce? I’ve annotated lines that potentially use a deprecated item.
1#[deprecated]2pub mod a {3 #[macro_export]4 macro_rules! foo {() => {};}5
6 pub fn bar() {}7
8 macro_rules! foo_no_export {() => {};}9 foo_no_export! {} // <-- 110}11
12#[deprecated]13macro_rules! baz {() => {};}14baz! {} // <-- 215
16use a::bar as bar1; // <-- 317use foo as foo1; // <-- 418
19fn main() {20 foo! {} // <-- 521 a::bar(); // <-- 622}You might think: 6.
There are 6 usages, so that’s what I expected as well.
Except, the answer is only 4.
It turns out, the inheriting of #[deprecated] through modules doesn’t work,
so 1 and 5 don’t produce a warning.
Though what surprised us was that 4 does give a warning!
What’s going on here is that the reason inheriting doesn’t work on macros, is that when a macro expands, we pretty much remove any trace of it from the AST. The macro is replaced by whatever the macro expanded to. Checking deprecation only happens later, once the macros are already gone, so their uses go unnoticed.
So why does 2 work then?
Turns out, there’s special code to handle deprecated on macros.
To make sure that in most cases it works as it is supposed to.
But, that codepath only works when #[deprecated] is used directly on a macro.
It doesn’t look up.
There’s more???
While playing around with what does and doesn’t work with #[deprecated],
we tried the following code.
Again, I’ve annotated lines that potentially use a deprecated item.
How many deprecation warnings do you think this produces?
1#[deprecated]2pub mod a {3 pub struct Foo;4 pub struct Bar();5 pub struct Baz {}6}7
8
9use a::Foo; // <-- 110use a::Bar; // <-- 211use a::Baz; // <-- 312
13fn main() {14 a::Foo; // <-- 415 a::Bar(); // <-- 516 a::Baz {}; // <-- 617}This time, it’s not fewer, it’s more!
Not 6, but 8.
1 and 2 count twice, since in a way they’re exporting two things.
Unit and tuple structs can be constructed without braces.
The way that works is that the compiler effectively does the following transformation:
1struct Foo2// roughly expands to3struct Foo {}4const Foo: Foo = Foo{};And use imports both definitions of Foo.
Hence, two warnings.
Structs with braces don’t get this treatment, and 3 only produces one warning.
This last bug is fixed with this pr.