Implicit module arguments


#1

I’ve run into situations where it makes sense to parameterize modules on types, constants, statics, or maybe other items. I’ve used a Haskell feature implicit parameters for functions, but they always felt tricky to mentally parse.

I wonder if providing only module level implicit parameters might make more sense? In particular, if I want an implicit parameter then it’s almost always because it’s a module wide, or even crate wide, configuration parameter, but I do not want to choose between making something a parameter everywhere vs using features and/or vendoring dependencies.

I’d rather side step any discussion of syntax by using a new keyword implicit which probably does not really work.

mod ParamaterizedModule {
    implicit type TypeParam : ParamaterTrait;
    implicit const ConstantParam : Type;
    ...
}

You can only reference or use items from ParamaterizedModule when both a TypeParam satisfying ParamaterTrait and a ConstantParam of type Type are in scope.

Rust would treat these as parameters for any items dependent upon them under the hood, but you could not specify them directly. A priori, an implicit const benefits from a degree of dependent types, but simpler schemes might work, perhaps only supporting static.

Is there reason to think that doing implicit parameters at the module level might make them more useful and comprehensible?


#2

This is basically the generic module rfc?


#3

Yes roughly, except this I felt module parameters could be passed implicitly. I’d missed that issue, thanks.


#4

Copying my reply from Github:

I think your proposal conflates things, in a way I find rather problematic.

  1. Declaration syntax
    • There do exist languages which declare module parameters in the body - Coq, for one.
    • However, it’s as I worded it - a language-wide thing. I feel this would be intensely jarring in Rust.
    • As you noted, it would require constructs that cannot be expressed in normal Rust.
    • implicit is not a keyword, IIRC, which has back-compat impacts.
  2. Invocation syntax
    • If you don’t pass them in at the “call site” (use), then what values do they get?
      • If they get inferred somehow, you now have cross-function inference, which is deeply problematic for many reasons (was discussed in-depth in the impl Trait RFC thread)
      • If they get defaulted, this is a duplicate syntax for defaulted parameters
        • If they get defaulted, how do you override the defaults? How are they parameters at all?
      • This is a non-issue if module-level parameters are just a shorthand for putting a parameter on every item, but it does rule out similar shorthand at the use site, which I would consider nice to have,
    • If you don’t pass them in at the call site, how does someone reading code where they are used learn what the types involved are?
    • Is it possible to import a module twice, with different parameters (using renaming)?

In short, I think that proposal would not work out well at all, and strongly prefer:

  • mod foo<'a, T: Trait, const x: usize = 3> { ... } - a declaration syntax that permits reusing all of the nice machinery we already have
  • use foo<Vec<u8>> as foo_vec, an invocation syntax that is explicit in its behavior, and has a clear meaning to anyone familiar with Rust.

If module-level parameters are merely a shorthand for additional parameters on the items of the module, the latter can be left to later, and eventually defined as a similar shorthand for setting those parameters.

EDIT: Ah, I’d missed your “use whatever’s fitting in-scope” bit. That’s the least workable part of this, IMO - it’s akin to Coq’s curly-braced parameter declarations, which really only work because Coq is a proof assistant, and can provide incredibly detailed bounds on them. Without that, I suspect they’d be the next thing to useless, because an unbounded type parameter could be satisfied so many ways it’s absurd.


#5

I think I agree actually. :slight_smile: There are not soo many use lines, so passing parameters there might just be fine, and they’ll pass in a nested way. If you mess up passing them inside your module, then it’ll fail to compile.