Non-prelude, but prelude-like pattern

I've read a few discussions about preludes over the years, and I've gathered that the Rust community is mostly against them (outside of std). I don't think I've ever seen anyone argue that they are always bad, but I'm curious why people don't like them. As far as I can tell there are two main objections:

  • They pollute the namespace, and may cause name collisions.
  • Explicitly naming all the imported types makes it much easier to see what crate types originate from.

For my part, the second point is the big one. I often use the imports to see where types come from.

With that said.. I have a couple of crates that I use frequently that have a few different types that in some sense belong together -- meaning that once I pull one in, I end up pulling one or more other types in as well. I sometimes end up with pretty long use-lines to do all the manual imports.

I was wondering if there was a way to have something between use somecrate::prelude::* and use somecrate::{Foo, Bar, Baz, SomethingMore, SomethingElse, EvenMore}. Like mini-preludes, that have a finer level of granularity (i.e. less namespace pollution).

Something akin to use somecrate::features::client::*, use somecrate::features::server::*.

Is this pattern in use anywhere? How frowned upon is it?

Most of the issues come from glob imports, not from preludes per-se. There's nothing especially problematic about having a somecrate::features::client that you import from, so that you do use somecrate::features::client::{Foo, Bar, Baz, SomethingMore, SomethingElse, EvenMore};, but glob imports can cause "interesting" issues.

There's two problems with glob imports:

  1. It makes it harder to track down where a given import comes from, especially once I have two or more glob imports in my module.
  2. Unlike explicit imports, which error if there's a name conflict, glob imports simply hide the thing that conflicts, delaying the ambiguity issue until first use.

For the first, consider two crates that both export a SocketOptions for their server-side. I do use somecrate::client::*; and use othercrate::server::*;, but because I don't realise that SocketOptions is a server-side only thing, and I'm not that familiar with either crate, I assume that SocketOptions has full path ::somecrate::client::SocketOptions. This leads to confusion in things like code review until I realise that my assumption is wrong, and that it's othercrate::server::SocketOptions.

For the second, this Rust Playground shows the hiding behaviour; both c1 and c2 export a struct Foo, and thus the glob imports silently fail to import either c1::Foo and c2::Foo as Foo. I'll get a point-of-use complaint if I attempt to use Foo, because it's ambiguous, but if I then added an explicit use of a Foo from a third location, it'd silently use that one, and open up another variant on the first error, where I think I'm using c1::Foo, because I'm thinking in a "the code here is all using things from c1" context, but I'm actually using a completely different c3::Foo.

And this just gets worse if instead of Foo being a struct, it's an extension trait with a method of the same name for all three variants; I think I'm using c1::Foo's extension method, but I'm actually using c3::Foo's extension method, and getting surprised when it doesn't behave the way c1::Foo is documented.

2 Likes