# Idea: pre-declared generic type parameters

**URL:** <https://internals.rust-lang.org/t/idea-pre-declared-generic-type-parameters/18130>\
**Category:** language design\
**Created:** [January 11, 2023, 9:43pm UTC](https://internals.rust-lang.org/t/idea-pre-declared-generic-type-parameters/18130 "2023-01-11T21:43:43Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![zackw](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/zackw/32/2071_2.png) [@zackw](https://internals.rust-lang.org/u/zackw)\
**Post date:** [January 11, 2023, 9:43pm UTC](https://internals.rust-lang.org/t/idea-pre-declared-generic-type-parameters/18130/1 "2023-01-11T21:43:43Z")

</div>

Generic type parameters often require you to repeat yourself a lot. Just to pick one example from the standard library:

```rust
impl<T> Option<T> {
    pub const fn unwrap_or(self, default: T) -> T
    where
        T: ~const Destruct,
    { ... }

    pub const fn unwrap_or_else<F>(self, f: F) -> T
    where
        F: ~const FnOnce() -> T,
        F: ~const Destruct,
    { ... }

    pub const fn map<U, F>(self, f: F) -> Option<U>
    where
        F: ~const FnOnce(T) -> U,
        F: ~const Destruct,
    { ... }
}

```

`impl<T> Option<T>` already looks redundant, but then we have all these `where` clauses as well, saying almost but not quite the same thing in each case. Wouldn't it be nice if we could factor that out somehow? Maybe like this?

```rust
type <
    T,
    U,
    ProduceT: ~const FnOnce() -> T,
    ConvertTU: ~const FnOnce(T) -> U,
    CanDestruct: ~const Destruct,
>;

impl Option<T> {
    pub const fn unwrap_or(self, default: T + CanDestruct) -> T {...}
    pub const fn unwrap_or_else(self, f: ProduceT + CanDestruct) -> T {...}
    pub const fn map(self, f: ConvertTU + CanDestruct) -> U {...}
}

```

This is intended to be a purely syntactic convenience. `type <T>; impl Option<T> { ... }` means exactly the same thing as `impl<T> Option<T>`. `type <T, U, Convert: FnOnce(T) -> U>; fn map(f: Convert) { ... }` means exactly the same thing as `fn<T, U, F> map(f: F) where F: FnOnce(T) -> U { ... }`.

The `type < ... >` notation is the first thing I thought of that avoided introducing a new keyword. If a new keyword is OK,

```rust
generic T;
generic ProduceT = ~const FnOnce() -> T;

```

might be more readable.

Like? Hate? Muffin?

---

<div class="post-metadata">

**Author:** ![scottmcm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/scottmcm/32/2355_2.png) [@scottmcm](https://internals.rust-lang.org/u/scottmcm)\
**Post date:** [January 11, 2023, 10:42pm UTC](https://internals.rust-lang.org/t/idea-pre-declared-generic-type-parameters/18130/2 "2023-01-11T22:42:40Z")

</div>

> [@zackw](#):
>
> `generic ProduceT = ~const FnOnce() -> T;`

That looks very similar to a trait alias?

```rust
trait Produce<T> = ~const FnOnce() -> T;

```

---

<div class="post-metadata">

**Author:** ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)\
**Post date:** [January 11, 2023, 11:05pm UTC](https://internals.rust-lang.org/t/idea-pre-declared-generic-type-parameters/18130/3 "2023-01-11T23:05:25Z")

</div>

The main issue is that this makes the generic list implicit. With APIT, we've said that implicit generics are acceptable, but that any implicit generics don't show up in the generics list for the purpose of turbofish application.

---

<div class="post-metadata">

**Author:** ![zackw](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/zackw/32/2071_2.png) [@zackw](https://internals.rust-lang.org/u/zackw)\
**Post date:** [January 12, 2023, 6:22pm UTC](https://internals.rust-lang.org/t/idea-pre-declared-generic-type-parameters/18130/4 "2023-01-12T18:22:18Z")

</div>

> [@CAD97](#):
>
> With APIT, we've said that implicit generics are acceptable

I don't know what APIT is?

> [@CAD97](#):
>
> The main issue is that this makes the generic list implicit

This proposal was not meant to make anything more implicit than it already is. I think I didn't do a very good job of explaining it, let me try again with a different running example. This is [the skeleton of the definition of `Option`](https://github.com/rust-lang/rust/blob/master/library/core/src/option.rs#L558), with irrelevant details erased:

```rust
pub enum Option<T> {
    None,
    Some(T),
}
impl<T> Option<T> {
    // ...
}

```

A programmer unaccustomed to either Rust or C++ might see this and think "Why must I write `<T>` three times?" The answer is that the type parameter T has to be repeatedly re-declared. `pub enum Option<T>` declares T as a type parameter, but it's only in scope for the rest of the item, so when we need it again for the impl block, we have to re-declare it. It is not obvious to me from the language reference why one may write `enum Option<T>` but not `impl Option<T>`, but that's not actually important. The point is, shouldn't there be a way to declare a type-parameter metavariable _once_ and make it available for use in the rest of the file? That's the heart of the proposal.

```rust
type <T>; // T is now known file-wide as a type parameter 
pub enum Option {
    None,
    Some(T),
}
impl Option {
    // ...
}

```

Again, this is **not** meant to imply that the type parameter for Option is now "implicit" in some sense. Its declaration has merely been factored out of its uses. The semantics of this code should be **identical** to the semantics of the original. I see this as much the same as how one need not write `pub fn is_some_and<T>` in

```rust
impl<T> Option<T> {
    pub fn is_some_and(self, f: impl FnOnce(T) -> bool) -> bool {
        ...
    }
}

```

The metavariable is already in scope, therefore it doesn't need to be re-declared.

---

<div class="post-metadata">

**Author:** ![scottmcm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/scottmcm/32/2355_2.png) [@scottmcm](https://internals.rust-lang.org/u/scottmcm)\
**Post date:** [January 12, 2023, 6:57pm UTC](https://internals.rust-lang.org/t/idea-pre-declared-generic-type-parameters/18130/5 "2023-01-12T18:57:21Z")

</div>

> [@zackw](#):
>
> The point is, shouldn't there be a way to declare a type-parameter metavariable _once_ and make it available for use in the rest of the file?

I think what I've previously seen that postulated as is generic `mod`ules.

So you could have `mod option<T> { enum Option { Some(T), None } }` etc.

Which of course gets into the same "can you say `Option<T>` for that?" questions as those coming up in [Allow importing assoicated methods - #2 by steffahn](https://internals.rust-lang.org/t/allow-importing-assoicated-methods/18131/2)

---

<div class="post-metadata">

**Author:** ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)\
**Post date:** [January 12, 2023, 7:48pm UTC](https://internals.rust-lang.org/t/idea-pre-declared-generic-type-parameters/18130/6 "2023-01-12T19:48:15Z")

</div>

> [@zackw](#):
>
> I don't know what APIT is?

Sorry, Argument Position Impl Trait. It's a standard enough initialism that I don't think to list it on irlo, though I usually will on urlo.

> [@zackw](#):
>
> Again, this is **not** meant to imply that the type parameter for Option is now "implicit" in some sense. Its declaration has merely been factored out of its uses. The semantics of this code should be **identical** to the semantics of the original.

The problem is the question of "can you apply the generics to the name."

With

```rust
fn example<T>(f: impl FnOnce(T));

```

you turbofish it as `example::<u32>(drop)`; you can apply an explicit binding for the `T` in the generics list, but the APIT cannot be specified, only inferred.

For

```rust
type <T>;
struct Example<U>(T, U);

```

the same should probably apply? Where `Example::<u32>(0, Default::default())` works and specifies `U` but leaves `T` unspecified and inferred.

Even if we didn't have an intermediate period where capturing ambient generics prevented the use of explicit generics instantiation and/or where mixing captured generics with explicit listed generics wasn't allowed, I'm 90% confident that existing generics lists wouldn't be able to be replaced with ambient generics, because ambient generics won't be allowed to be specified in generics lists. And for that reason, I don't see ambient generic capture as likely to happen; APIT is one thing and typically intended to be used when the type will already be known at the call site (and potentially unnamable), but ambient generics make too many generics unturbofishable.

What I _could_ see happening is a severely restricted form of it, e.g. something like

```rust
for<T: ~const Destruct>;

impl Option<T> {
    // ...
}

```

being allowed, where you define a name usable in `impl` headers only which isn't resolved to a concrete type, but instead makes the `impl` block generic over the type as declared.

This also sidesteps what I see the biggest problem with generic modules to be: what about items which _don't_ "capture" the ambient generic? Are they still associated to the generic type and monomorphized for each one? (For `fn` the answer can both be semantically yes but trivially optimized to no, but it's plainly observable for `static`.)

---

<div class="post-metadata">

**Author:** ![zackw](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/zackw/32/2071_2.png) [@zackw](https://internals.rust-lang.org/u/zackw)\
**Post date:** [January 12, 2023, 8:22pm UTC](https://internals.rust-lang.org/t/idea-pre-declared-generic-type-parameters/18130/7 "2023-01-12T20:22:08Z")

</div>

> [@CAD97](#):
>
> > [@zackw](#):
> >
> > The semantics of this code should be **identical** to the semantics of the original.
> 
> The problem is the question of "can you apply the generics to the name."
> 
> With
> 
> ```rust
> fn example<T>(f: impl FnOnce(T));
> 
> ```
> 
> you turbofish it as `example::<u32>(drop)`; you can apply an explicit binding for the `T` in the generics list, but the APIT cannot be specified, only inferred.
> 
> For
> 
> ```rust
> type <T>;
> struct Example<U>(T, U);
> 
> ```
> 
> the same should probably apply? Where `Example::<u32>(0, Default::default())` works and specifies `U` but leaves `T` unspecified and inferred.

I feel like you are imagining a much more ambitious feature than what I have in mind.

When I say "the semantics should be identical", I mean that

```rust
type <T>;
struct Example<U>(T, U)

```

should behave **exactly the same** as

```rust
struct Example<T, U>(T, U);

```

with regard to all possible uses of `Example`. I **only** want to reduce the number of times people have to write `<T>` in **declarations**. (And it would be nice if I could also reduce the number of times people have to write an identical `where` clause.) No changes to points of use are intended.

If this leaves you wondering why bother, consider:

- The fewer times people have to write down a name, the less likely they are to typo it.
- And the more likely they are to make it a _descriptive_ name.
- It is quite difficult to explain to a newcomer to the language why the impl block for `struct Example<T, U>(T, U)` has to be opened with `impl<T, U> Example<T, U>`. In fact, the only way I know how to explain it is in language specification jargon.

(Also, I do not think this hypothetical feature should replace the ability to specify an explicit generic type parameter list.)

---

<div class="post-metadata">

**Author:** ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)\
**Post date:** [January 12, 2023, 10:01pm UTC](https://internals.rust-lang.org/t/idea-pre-declared-generic-type-parameters/18130/8 "2023-01-12T22:01:16Z")

</div>

> [@zackw](#):
>
> When I say "the semantics should be identical", I mean that
> 
> ```rust
> type <T>;
> struct Example<U>(T, U)
> 
> ```
> 
> should behave **exactly the same** as
> 
> ```rust
> struct Example<T, U>(T, U);
> 
> ```
> 
> with regard to all possible uses of `Example`.

I understand the _why_, it's just that APIT presents prior art that "implied" generics not present in the explicit generics list aren't specified when turbofishing the explicit generics list. If "desugaring" them into the generics list, you also have to answer what the order is, both to with the explicitly introduced generics and with each other. Saying "the order the ambient generics were introduced in" isn't all that satisfying, either, since Rust code is so far completely item definition order independent. You could resolve _this_ by saying all captured ambient generics must come from a single ambient generic list which defines the relative order that they're in, but at this point we're piling on so many restrictions for just a little bit of sugar.

> [@zackw](#):
>
> I feel like you are imagining a much more ambitious feature than what I have in mind.

Actually, I'm expecting that the feature would be initially restricted like APIT was, such that use of the feature _entirely prevents_ use sites to apply an explicit generics list to an item capturing ambient generics, to entirely sidestep the question initially (like was done for APIT).

I'm presenting the analogy to APIT not to say that such a feature SHOULD behave that way, but that it's not immediately obvious that it would just be sugar for prepending (or postpending) the list of captured ambient generics to the explicit generics list and thus be trivially turbofishable.

> [@zackw](#):
>
> It is quite difficult to explain to a newcomer to the language why the impl block for `struct Example<T, U>(T, U)` has to be opened with `impl<T, U> Example<T, U>`. In fact, the only way I know how to explain it is in language specification jargon.

Without (much) jargon, the way I answer to this is roughly along the lines of:

If you just write `impl Option<T>`, there's no way of knowing whether you're intending to refer to a generic type `T` or to a concrete type `T`. The compiler could guess that if `T` isn't a concrete type in scope it should be a generic type, but Rust as a language doesn't like to make guesses like that; it could be that `T` is a typo of some other type, or that you forgot to import some type `T`. The inference could limit the risk by only treating single-letter types are generic names (e.g. meaning `Instant` would be correctly caught as a missing import), but Rust doesn't like to give semantic meaning to conventional naming schemes; this is why the `nonstandard_style` lint is just a warning rather than an error.

Aside: The compiler actually had used to have a feature almost exactly like this, but restricted to lifetimes, called [in-band lifetimes](https://github.com/rust-lang/rust/issues/44524), where using an undeclared lifetime would automatically declare it in on the containing `fn` signature. It was removed for essentially this exact reason -- it was unclear whether a lifetime was intended to come from the containing scope or be a fresh in-band lifetime.

You could alternatively say that if the name exactly matches the generic introduced on the `struct` definition, then it should be treated as a generic declared the same way. This is certainly a very reasonable choice, and the unstable [`implied_bounds`](https://github.com/rust-lang/rust/issues/44491) feature essentially gives half of that (the bounds but not the generics). The reason this isn't done is perhaps simply historical; the choice of generic placeholder names has historically been an exclusively local choice for documentation without any impact on downstream code. In a way, it's analogous to how function argument names are private implementation details with no impact on downstream code, only shown for documentation purposes.

* * *

Personally, I'd be completely fine with "ambient generics" used for `impl` headers specifically, or even implied bounds being extended to implied generics for the generic names used on the type declaration. It's solely on other generic introducer lists where I feel there are too many nonobvious behaviors involved. "Explicit" is of course [an overloaded term](https://boats.gitlab.io/blog/post/2017-12-27-things-explicit-is-not/), but personally I think breaking the `fn foo<A, B>`/`foo::<_, _>` equivalent arity is more problematic than the reduction in boilerplate is beneficial.

Note also that `~const` is an experimental/internal-only syntax currently, and has not gone through any RFC process\[1\]. `~const` essentially doesn't exist yet. An ambient generics feature is still useful for normal trait bounds, but the noise overhead is much lower without `~const`.

For anyone who isn't stdlib using pre-RFC experimental features, I'd suggest that they write the OP example as

```rust
impl<T> Option<T> {
    pub fn unwrap_or(self, default: T) -> T
    { ... }

    pub fn unwrap_or_else(self, f: impl FnOnce() -> T) -> T
    { ... }

    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Option<U>
    { ... }
}

```

Much less noise. The lack of ability to turbofish the `impl FnOnce` is almost immaterial, as in most cases the type is unnamable anyway.

For cases like writing tokio, tracing, and/or tower where you can end up with giant `where` clauses repeating essentially the same bounds everywhere, the solution imho is [trait aliases](https://github.com/rust-lang/rust/issues/41517) first.

* * *

1. Personally, I'm still sad that `~const` couldn't be the default for `const fn`; the ability to have bounds on `const fn` _accidentally_ slipped in and wasn't completely gated, which in part led to the reasonable choice to allow normal nonconst trait bounds in `const fn` on stable, so stable `const fn` could use e.g. associated `const`. It's a reasonable choice, but one that unfortunately leads to most trait bounds in `const fn` being annotated `~const` unless we switch the behavior in an edition. This falls to the domain of the keyword generics initiative to figure out.

---

<div class="post-metadata">

**Author:** ![afetisov](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/afetisov/32/8508_2.png) [@afetisov](https://internals.rust-lang.org/u/afetisov)\
**Post date:** [January 13, 2023, 8:50am UTC](https://internals.rust-lang.org/t/idea-pre-declared-generic-type-parameters/18130/9 "2023-01-13T08:50:23Z")

</div>

The things you propose is quite similar to [trait aliases](https://github.com/rust-lang/rfcs/pull/1733) and [constraints](https://github.com/rust-lang/rfcs/pull/1733#issuecomment-283511181). I don't know why they are in a limbo. I couldn't find an RFC for constraints, surprisingly. Looks like that's just something people regularly mention in the comments on trait aliases.

---

<div class="post-metadata">

**Author:** ![dlight](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/dlight/32/8462_2.png) [@dlight](https://internals.rust-lang.org/u/dlight)\
**Post date:** [January 13, 2023, 4:23pm UTC](https://internals.rust-lang.org/t/idea-pre-declared-generic-type-parameters/18130/10 "2023-01-13T16:23:21Z")

</div>

> [@zackw](#):
>
> ```rust
> 
> ```
> 
> This is intended to be a purely syntactic convenience. `type <T>; impl Option<T> { ... }` means exactly the same thing as `impl<T> Option<T>`.

It looks like you want something that works like a macro, but is called `like_this<..>;` instead of `#[like_this]` or `like_this! { .. }` (but baked into the language, but that's not a big distinction, since there are things `#[like this]` and `like_this! { }` baked into the lang)

So... what about putting `#![generic<T>]` on top of the file? meaning it applies a `<T>` into every impl in the whole module.

---

<div class="post-metadata">

**Author:** ![bjorn3](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/bjorn3/32/2736_2.png) [@bjorn3](https://internals.rust-lang.org/u/bjorn3)\
**Post date:** [January 13, 2023, 7:47pm UTC](https://internals.rust-lang.org/t/idea-pre-declared-generic-type-parameters/18130/11 "2023-01-13T19:47:15Z")

</div>

> [@scottmcm](#):
>
> So you could have `mod option<T> { enum Option { Some(T), None } }` etc.
> 
> Which of course gets into the same "can you say `Option<T>` for that?" questions as those coming up in [Allow importing assoicated methods - #2 by steffahn](https://internals.rust-lang.org/t/allow-importing-assoicated-methods/18131/2)

I think in this case you would write `option::<T>::Option`. That is specify it when referencing the module that has the generic parameter.

---

<div class="post-metadata">

**Author:** ![system](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/system/32/14092_2.png) [@system](https://internals.rust-lang.org/u/system)\
**Post date:** [April 13, 2023, 7:48pm UTC](https://internals.rust-lang.org/t/idea-pre-declared-generic-type-parameters/18130/12 "2023-04-13T19:48:09Z")

</div>

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.
