(I wrote this up in response to #t-lang > mod _
instead of const _
?)
Motivation
Rust modules broadly serve three purposes:
-
API organization: Modules are used to group related items together.
std
is a great example of this:std::fs
contains filesystem-related functionality,std::fmt
formatting stuff, and so on. -
File organization: Modules are used to split the internal implementation over multiple files, without affecting the public API. Again using
std
as an example,vec::Drain
is defined like this:pub use self::drain::Drain; mod drain;
It's not uncommon to instead write
pub use self::drain::*
. -
Field access Restrictions: Modules can be used to limit which parts of the code have direct access to a
struct
's fields.This is especially useful if the fields need to maintain some kind of invariant: A small number of carefully-implemented, low-level APIs have direct access to the fields and always maintain the invariants, and higher-level APIs are implemented in terms of the lower-level ones, without direct access to the fields, and thus without needing to worry about the invariants.
I've personally written code like the following (though don't have an example from some prominent ecosystem crate):
mod foo_fields_acessible { pub struct Foo { ... } impl Foo { // ... low-level API } } pub use self::foo_fields_accessible::*; impl Foo { // ... high-level API }
The pub use
approach works reasonably well, but I've personally run into a number of annoyances with it:
- It feels like unnecessary overhead.
- rust-analyzer gets confused about where to add new imports.
- If I do
pub use self::child::*
for all my child modules for consistency, but one of the children has at mostpub(crate)
items, thenunused_imports
complains. - Declarative macros can't easily generate
*_foo_accessible
modules.
Most of the individual annoyance can probably be fixed by improving the indivdual features, but purposes (2) and (3) seem common enough that supporting them with a dedicated syntax seems reasonable to me.
Explanation
(The "transparent" terminology is open for discussion).
TODO: Verify that
transparent mod
can be implemented as a weak keyword
Transparent modules are modules that do not affect the public API.
pub mod vec {
pub transparent mod drain {
pub struct Drain { ... }
}
}
// This is fine
fn test() -> self::vec::Drain { ... }
// This is an error
fn test() -> self::vec::drain::Drain { ... }
Transparent modules may be unnamed if they are inline or have a #[path = "..."]
attribute:
pub transparent mod {
pub struct Foo { ... }
impl Foo {
// ... low-level API
}
}
impl Foo {
// ... high-level API
}
$vis transparent mod foo;
is essentially equivalent to mod foo; $vis use self::foo::*
;
The following behaviors of modules are unaffected by this RFC:
- The parent module's items are not automatically in scope, not even for transparent inline modules.
- The parent module can be accessed via
super::
.
It is an error for multiple transparent modules, or the parent module, to define items with conflicting names.
Transparent modules include a visibility qualifier for consistency with non-transparent modules, and to allow transparent modules to use pub
on its own items to indicate its own public API, while still letting the parent control how far the those items should actually be exposed.
The compiler should:
- Not print any warnings if all items in a transparent module are less visible than the module's visibility.
- If it does print a warning, it should be a new, dedicated lint.
- Use the following rules when needing to printing the full path to an item defined in a transparent module:
- If the item is public, i.e. accessible outside the transparent module, do not include the name of the transparent module in the path.
- If the item is private, i.e. not accessible outside the transparent module:
- If the transparent module is named, use the module's name, optionally with some indication that the module is transparent (e.g.
std::vec::{drain}::PrivateSomething
). - If the transparent module is unnamed, synthesize an identifier, similar to what is done for closures (e.g.
{transparent@<location>}
).
- If the transparent module is named, use the module's name, optionally with some indication that the module is transparent (e.g.