- Feature Name: inline_mod
- Start Date: 2017-08-04
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)
Summary
One very common boilerplate in Rust is the facade pattern, where additional submodules
are created to have code separation at the file level, but present that as a single
module in the external interface. The futures
crate is a great example of this.
This RFC would introduce the inline
prefix to the existing mod
statement
which would make that particular module anonymous, therefore, replacing lots of
boilerplate with a single additional word.
Motivation
Iām going to paraphrase Aaron Turonās recent modules post:
Letās take a concrete example from the futures crate. Futures, like iterators, have a large number of methods that produce āadaptersā, i.e. concrete types that are also futures:
trait Future {
type Item;
type Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error>;
fn map<F, U>(self, f: F) -> Map<Self, F>;
fn then<F, B>(self, f: F) -> Then<Self, B, F>;
// etc
}
Each of these concrete types (Map, Then and so on) involve a page or so of code, often with some helper functions. Thus, there was a strong desire to define each in a separate file, with the helper functions private to that file.
However, in Rust each file is a distinct module, and it was not desirable to have a large number of submodules each defining a single type. So, instead, the future module has code like this:
mod and_then;
mod flatten;
mod flatten_stream;
mod fuse;
mod into_stream;
mod join;
mod map;
mod map_err;
mod from_err;
mod or_else;
mod select;
mod select2;
mod then;
mod either;
pub use self::and_then::AndThen;
pub use self::flatten::Flatten;
pub use self::flatten_stream::FlattenStream;
pub use self::fuse::Fuse;
pub use self::into_stream::IntoStream;
pub use self::join::{Join, Join3, Join4, Join5};
pub use self::map::Map;
pub use self::map_err::MapErr;
pub use self::from_err::FromErr;
pub use self::or_else::OrElse;
pub use self::select::{Select, SelectNext};
pub use self::select2::Select2;
pub use self::then::Then;
pub use self::either::Either;
This kind of setup is known generally as the facade pattern, and itās pretty ubiquitous in Rust code.
The facade boilerplate is needed to deal with a misalignment: each adapter is defined in its own file with its own privacy boundary, but we donāt actually want that to entail a distinct module for each (in the internal or external namespace hierarchy). That means we have to do two things:
- Make the modules private, despite that they contain public items
- Manually re-export each of the public items at a higher level
When first trying to navigate the futures codebase, you have to read the future module to understand how its submodules are being used, due to these re-exports. For the futures crate, this is a relatively small annoyance. But it can be a real source of confusion for crates that have more of a mixture of submodules, some of which are significant for the namespace hierarchy, other of which are hidden away.
However, this proposal is not trying to solve the problem of finding the location of an item, given its extern module-based path. The facade pattern is premised on hiding this location and any feature which facilitates this pattern fundamentally cannot help with this problem.
Guide-level explanation
In the case of the futures crate, the it would change to be the following with
inline mod
:
inline mod and_then;
inline mod flatten;
inline mod flatten_stream;
inline mod fuse;
inline mod into_stream;
inline mod join;
inline mod map;
inline mod map_err;
inline mod from_err;
inline mod or_else;
inline mod select;
inline mod select2;
inline mod then;
inline mod either;
The addition of inline
to mod
would desugar into the following:
mod my_mod;
pub use my_mod::*;
Items within the module are private by default, unless marked with some version of pub
.
Reference-level explanation
inline mod my_mod;
is desugared exactly into
mod my_mod;
pub use my_mod::*;
Visibilities on inline modules are prohibited as confusing. Due to filtering properties
of glob imports pub use my_mod::*;
keeps visibilities of items defined in my_mod
intact when reexporting, i.e. pub
items will be reexported as pub
, pub(crate)
items
will be reexported as pub(crate)
, etc.
The inline
modifier should be implementable as a contextual keyword, which
makes it backwards compatible (along with the fact that the behavior of existing
code does not change).
Migrating crates from using the current facade pattern to this feature should also be backwards compatible, assuming the relevant changes are made to various privacy modifiers.
Drawbacks
- Perhaps people prefer the current pattern because it is more flexible.
- This proposal adds a small amount of complexity and the benefit may not outweigh the costs.
Rationale and Alternatives
- This design is focused on removing boilerplate through the addition of a targeted feature, which is backwards compatible. Most other designs which accomplish the same thing, are not backwards compatible or propose additional functionality which is not targeted at reducing boilerplate.
- Alternate designs:
- The procedural macro system of Rust could be extended to allow an attribute on a
mod
statement like#[facade] mod foo;
which would create the facade pattern automatically. This would result infoo
being declared as an item in scope, but other than that would likely have the same effect. Though, this solution would require completely different RFC, with implications for macros 2.0, I imagine. Using#[inline]
would be confused with the other attribute with this name. - The impact of not doing this is that the boilerplate would continue when implementing facade pattern.
Unresolved questions
- None remaining.