Pub use Trait as _ for more hygiene

Sometimes you want a module or crate to provide a prelude (see rayon's prelude for an example), which allows you to easily bring several types, traits, or enums into scope.

In some cases, you only want the traits' methods being usable, but you don't want the traits to be available with their names (especially when they have names such as Error, which could collide). For these cases, there exists the underscore import:

use some_module::SomeTrait as _;

This works fine as long as there are a handful traits. But what if you have 10 or 20 or more of these traits in a module that you want to use? Can we combine underscore imports with a prelude?

The naïve attempt fails:

use rayon::prelude::* as _;

fn main() {}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error: expected `;`, found keyword `as`
 --> src/main.rs:1:23
  |
1 | use rayon::prelude::* as _;
  |                       ^^ expected `;`
  |
  = note: the wildcard token must be last on the path

error: could not compile `playground` due to previous error

When thinking on how to solve this, I stumbled upon an interesting sentence regarding underscore imports in the reference:

Asterisk glob imports will import items imported with _ in their unnameable form.

With that, we can do the following:

pub struct S {}

pub mod m {
    pub mod prelude {
        pub use super::T as _; // <- this re-exports `T` in an unnameable form
    }
    pub trait T {
        fn foo(&self) {
            println!("foo!");
        }
    }
    impl T for super::S {}
}

use m::prelude::*;

fn main() {
    let s = S {};
    s.foo();
    // so `T` is not in scope:
    //let _d: Box<dyn T>; // <- won't work
    let _d: Box<dyn m::T>; // we need qualified syntax!
}

(Playground)

In the above example, our module m provides a submodule m::prelude. Given how the prelude module is designed in this particular case, we can import all re-exported traits of this submodule with

use m::prelude::*;

which brings T (and possibly many more traits that we add in future) into scope, without occupying any name for the trait. If we want to access T, we still need to use the qualified syntax m::T instead of T.

Whether this approach is suitable for your own prelude(s) might depend on the particular names of your trait(s) and if there's a high chance of name collisions. (Note that there can still be collisions regarding the method names, of course.)

In either case, I find this feature noteworthy.

2 Likes

FWIW, I wrote an RFC for this two years back, but it was closed: RFC: impl-only glob imports by jplatte · Pull Request #2782 · rust-lang/rfcs · GitHub

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.