In a discussion about RFC #2377, Item-level-blocks, @fknorr floated the idea of selling that RFC as "anonymous modules" which:
[..] has precedent with C++'s concept of anonymous namespaces, where
namespace {}
introduces an unnameable scope with the parent automatically importing all definitions.
Syntactically, you would then write:
#[cfg(debug_assertions)]
mod {
// ...
}
On its own however, being able to write mod { .. }
just so that you may #[cfg(debug_assertions)]
did not seem develop consensus.
However, I have found another motivation for this syntax: not repeating type parameters. This idea is inspired by the using (x : a, y : a, xs : Vect n a)
notation in Idris.
Simply put, we call mod { .. }
an inline module (IM), since all of its contents are inlined into the enclosing scope. We then extend this with the ability to quantify and bound variables:
mod<'a, T, U: Copy, const N: usize> {
...
}
Items enclosed in this parameterized inline module (PIM) are all prepended with the list of parameters in the mod<...>
. A simple example:
mod<T: Default> {
fn foo<U>() -> T { T::default() }
}
fn main() {
assert_eq!(0, foo::<u32, bool>());
}
This is imagined as primarily useful for generics heavy code that work within the same generic context over and over again. An example of this is diesel, which repeats DB: Backend
over and over again...
Some things to note:
-
struct
s,enum
s,union
s andimpl
s, insidemod<..>
must all use all the quantified parameters insidemod<..>
. Otherwise, there are unused or unconstrained parameters, which Rust does not allow. -
static
s are not allowed inside PIMs because doing so would introduce genericstatic
s, which can cause soundness issues due to the same sequence of concrete types applied, at different places, to the genericstatic
ending up with different addresses in memory. This is perhaps a fundamental limitation, but perhaps not. -
This is one stop short of ML modules. Introducing those would be done by allowing you to name and quantify at the same time:
mod foo<T: Bound> { .. }
. While being an interesting idea, this is not proposed at this time. It might however be that taste or expectation develops for those over time due to their inline cousins being added. -
Implied bounds alleviate some of the problems PIMs are trying to solve, but not fully.
-
If people abuse this feature, by adding too many things in the PIM, understanding can get harder, but this is not automatic. PIMs, like other current or would-be language features, are tools at your disposal, so use them tactfully. The same goes with cyclomatic complexity in
fn
bodies, overly complex types, and so on. -
Prepending was chosen as the thing I thought was most obvious; but if there is consensus that Appending is more natural, I'm totally down with that. I don't have a strong feeling either way.
Regarding the arbitrary nature of making a choice at all, I think it is fine to have such conventions, as long as they are applied consistently.
An argument can be made that prepending is better for const dependent types (const generics) but that appending is better for turbofishing since the context could be assumed to be more inferrable.
-
Is inline in PIM the right word? Is anonymous (PAM) more appropriate?
Right now, this is a very unbaked idea. But I thought I'd spark a discussion. Thoughts?