Impl trait initiative

initiative status: active

What is this?

This page tracks the work of the Impl trait initiative! To learn more about what we are trying to do, and to find out the people who are doing it, take a look at the charter.

Current status

This is an umbrella initiative and, as such, it covers a number of subprojects.

SubprojectIssueProgressStateStage
impl Trait in fns and inherent methods#123▰▰▰▰▰▰Stabilized
type alias impl trait#63063▰▰▰▰▱▱🦀Development
impl trait in traits, impls#123▰▰▱▱▱▱🦀Experimental
impl trait in bindings#63065▰▰▰▱▱▱💤Development
impl trait with multiple types▰▱▱▱▱▱💤Experimental
existential lifetimes▰▱▱▱▱▱💤Experimental

Key:

  • ✅ -- project complete
  • 🦀 -- project in progress
  • 💤 -- project not under active development

How Can I Get Involved?

  • Check for 'help wanted' issues on this repository!
  • If you would like to help with development, please contact the owner to find out if there are things that need doing.
  • If you would like to help with the design, check the list of active design questions first.
  • If you have questions about the design, you can file an issue, but be sure to check the FAQ or the design-questions first to see if there is already something that covers your topic.
  • If you are using the feature and would like to provide feedback about your experiences, please [open a "experience report" issue].
  • If you are using the feature and would like to report a bug, please open a regular issue.

We also participate on Zulip, feel free to introduce yourself over there and ask us any questions you have.

Building Documentation

This repository is also an mdbook project. You can view and build it using the following command.

mdbook serve

📜 Impl trait Charter

Proposal

impl-trait is an umbrella initiative that is generally developing the impl Trait syntax in Rust. The overall goal is to enable users to write impl Trait to mean "some type implemementing Trait". We wish to enable this syntax in as many places as it makes sense to do so, and to develop a coherent philosophy around its meaning.

Membership

RoleGithub
Ownernikomatsakis
Liaisonghost

✏️ Updates

Lang-team initiatives give monthly updates. This section collects the updates from this initiative for posterity.

2021-Oct: Lang team update

Summary

  • oli-obk completed a rewrite of the inference engine and is in the process of landing it.
  • oli-obk is also pursuing a rewrite of the region checker to use intersection regions to address some of the shortcomings in the current system.
  • spastorino wrote a number of test cases covering the "feature surface area".
  • spastorino removed the (incorrectly implemented) support for let x: impl Trait; it will be re-evaluated later.
  • nikomatsakis is working on an explainer covering the "big picture" of TAITs.
  • tmandry + nikomatsakis are planning a proposal to permit users to name the types of associated functions from traits or inherent impls.
    • This opens the way for -> impl Trait notation in traits.

Goals for this month

  • Prepare stabilization report
  • Land oli's rewrite
  • Extend explainer
  • Extend evaluation doc to cover naming of impl traits

Questions for discussion

  • How long should we allow new inference engine to "bake" before stabilizing?
    • We have a large suite of test cases.
    • The inference engine is used for all impl Trait, including -> impl Trait and async fn, so it gets a lot of testing once it hits stable.

Type alias impl trait stabilization

The major focus remains "type alias impl trait" (TAIT) stabilization:


#![allow(unused)]
fn main() {
type Foo = impl Trait;

impl SomeTrait for SomeType {
    type AssocType = impl Debug;

    ...
}
}

Inference engine rewrite

oli-obk has completed a rewrite of the TAIT inference engine so that it matches the inference behavior of the RFC. This rewrite has been largely completed on a branch and is in the process of landing. It requires a number of supporting changes, such as improvements to NLL diagnostics.

The semantics that were implemented are briefly defined as follows. First, each TAIT has a "defining scope" based on where it appears:

  • In a module-level type alias: the defining scope is the enclosing module.
  • In the value of an associated type: the defining scope is the impl.

Within that defining scope, each item (function, etc) that references the TAIT defines a value for the TAIT based on the results of type-checking. Essentially, the TAIT's value is assumed at first to be an unbound inference variable, and we solve for whatever value of that variable will make the function type check. Each item must completely define the hidden type without relying on other items; further, each item must provide the same value for the hidden type. Example:


#![allow(unused)]
fn main() {
mod tait {
    pub type TAIT = impl Debug;

    // `foo` infers TAIT to be `u32`,
    // because that is the value needed
    // to make it type check:
    pub fn foo() {
        let x: TAIT = 22_u32;
    }

    // `get` also infers TAIT to be `u32`,
    // because that is the value needed
    // to make it type check:
    pub fn get() -> TAIT {
        22_u32;
    }

    // `take` also infers TAIT to be `u32`,
    // because that is the value needed
    // to make it type check. Calling `take`
    // from *outside* the module would not
    // be possible except by providing a return
    // value from `get`.
    pub fn take(x: TAIT) {
        let y: u32 = x;
    }

    // Other also constraints TAIT to be `u32`,
    // albeit indirectly.
    pub fn other() {
        take(22_u32);
    }
}
}

Outside of the defining scope, references to the type TAIT are opaque. The code is not permitted to rely on the hidden type, though it may peek at the auto traits implemented by the hidden type:

mod tait { ... /* as above */ ... }

fn main() {
    // Allowed
    tait::take(tait::get());

    // ERROR-- The hidden type is not visible outside of 
    // the module `tait`, so we can't rely on it being
    // `u32`.
    tait::take(44_u32);

    // ok -- TAIT is an `impl Debug`
    is_debug::<tait::TAIT>(); 

    // ok -- auto traits can rely on the hidden type,
    // and `u32: Send`
    is_send::<tait::TAIT>();

    // error -- even though `u32` implements `Display`,
    // we don't know the hidden type
    is_display::<tait::TAIT>();
}

fn is_send<T: Send>() { }
fn is_debug<T: Debug>() { }
fn is_display<T: Display>() { }

Naming impl Trait in traits

We would like to support -> impl Trait in traits and impls. One of the questions that has been blocking progress is the question of how to name the resulting associated type. In other words, the syntax


#![allow(unused)]
fn main() {
trait Foo {
    fn bar(&self) -> impl Debug + '_;
}
}

is equivalent to something like this but where Bar is an implicit associated type


#![allow(unused)]
fn main() {
trait Foo {
    type Bar<'me> = impl Debug + 'me;
    fn bar(&self) -> Self::Bar<'_>;
}
}

The problem is that users have no way to name Bar, and hence no way to name the return type of bar.

There have been numerous proposals to address this, but the most straightforward -- and also the most enabling -- seems to be allowing users to get access to the "zero-sized fn type" that represents bar.

The idea would be to define an associated type for every fn item, so that Foo::bar identifies the associated type for this fn in general, and <T as Foo>::bar identifies the associated type for the function as implemented for the type T.

This permits you to access the return type by writing T::bar::Output, for example, although we will have to extend the type checker to accommodate relative associated types like that (the full syntax would be <<T as Foo>::bar as FnOnce>::Output).

These are the known questions to address in the evaluation doc:

  • Can you name top-level functions or other sorts of functions?
    • What about the constructor functions for tuple structs or enum variants?
  • How to manage associated types and fns with the same name?
    • Do we have an unambiguous syntax such as T::fn(bar) to resolve that?
      • (This might apply to constructor functions as well.)

🔬 Evaluation

The evaluation surveys various specific questions that have arisen over the course of the impl trait design and gives a detailed write-up of the alternatives that were considered. See the table of contents on the left for the list of questions.

APIT and Turbofish

Summary

Introduction

impl Trait in argument position introduces a new generic parameter to the function:


#![allow(unused)]
fn main() {
fn collect_to_vec<T>(iter: impl Iterator<Item = T>) -> Vec<T> {
    iter.collect()
}

// is roughly equivalent to:

fn collect_to_vec_desugared<T, I>(iter: I) -> Vec<T>
where
    I: Iterator<Item = T>,
{
    iter.collect()
}
}

With the desugared version, users can write an expression that manually specifies the values for its type parameters:


#![allow(unused)]
fn main() {
let f = collect_to_vec_desugared::<u32, vec::IntoIter<u32>>;
}

The question addressed here is whether it should be possible to write an equivalent expression for collect_to_vec.

Current behavior

The compiler today prevents the use of turbofish for any function with APIT. This is a future compatibility restriction and not a long term plan.

Observations

Users should not have to acknowledge impl Trait during turbofish, that'd be confusing

Today, when using turbofish form, we require that users write a value for all generic type parameters, even if that value is _ (we allow lifetime parameters to be elided if the user chooses, and in fact require them to be elided in some cases). Therefore, it would be an error to write collect_to_vec_desugared::<u32>(vec.into_iter()). It is allowed to write collect_to_vec_desugared::<u32, _>(vec.into_iter()), however, and thus to lean on type inference for one parameter.

While APIT is "roughly equivalent" to a new generic parameter, this parameter is not listed explicitly in the list of generics. Therefore, it would likely be surprising for users to get an error for not giving it a value. This argues that collect_to_vec::<u32>(vec.into_iter()) should be accepted.

If we wish to allow turbofish to specify the value for an impl Trait, we would need to linearize the list

Furthermore, if we wished to extend impl Trait parameters into the list of type parameters, we would have to come up with an ordering. Presumably it would be "left to right" as they are written within the arguments.

Some type parameters are given explicit values more often than others and Rust doesn't let one make that distinction

It sometimes happens that functions have some type parameters which are useful (even mandatory) to specify, and others which are not. This usually happens when some type parameters appear in the argument list, and hence can be inferred from the call arguments, but others either do not appear anywhere or appear only in the return type. Example:


#![allow(unused)]
fn main() {
fn log_the_things<D: DebugLog>(strings: impl IntoIterator<Item = String>) {
    for s in strings {
        D::log_str(s);
    }
}
}

Here, it would be common for users to wish to write something like log_the_things::<SomeLoggerType>(some_vector), where the type D is manually specified but the value of the impl IntoIterator is inferred from the argument. Without using impl Trait, this would have to be written log_the_things::<SomeLoggerType, _>(...).

You sometimes want to get a function type without calling it

In all the examples so far, we have assumed that it was possible to infer the value of an impl Trait argument from the arguments that were supplied. But that might not always be true. Consider this rather artificial example:


#![allow(unused)]
fn main() {
struct Foo<T> {
    t: T
}

fn test_fn(x: impl Iterator) {
    
}

fn make() -> Foo<impl Debug> {
    Foo { x: test_fn }
}
}

Here, we are not calling test_fn, just taking its value.

Alternatives

Allow users to the values for impl Traits, but do not require it

📚 Explainer

The "explainer" is "end-user readable" documentation that explains how to use the feature being developed by this initiative. If you want to experiment with the feature, you've come to the right place. Until the feature enters "feature complete" form, the explainer should be considered a work-in-progress.

Overview

This explainer describes the "impl Trait" feature as a complete unit. However, not all parts of this story are in the same state. Some parts are available on stable, others are available on nightly, others have accepted RFCs but are not yet implemented, and still others are in the exploration phase. Each section of the explainer has a "status" tag that indicates whether the status of that particular content.

Impl trait: make the compiler do the hard work of figuring out the exact type

Status: Stable

Oftentimes, you have a situation where you don't care exactly what type something has, you simply care that it implements particular traits. For example, you might have a function that accepts any kind of iterator as long as it produces integers. In this case, you could write the following:


#![allow(unused)]
fn main() {
fn sum_integers(integers: impl Iterator<Item = u32>) -> u32 {
    //                    ^^^^^^^^^^^^^
    let mut sum = 0;
    for integer in integers {
        sum += x;
    }
    sum
}
}

Here, the impl Iterator type is a special sort of type. It doesn't actually name any particular type. Instead, it is a shorthand for a generic type. In other words, the function above is roughly equivalent to the following desugared version:


#![allow(unused)]
fn main() {
fn sum_integers<I>(integers: I) -> u32
where
    I: Iterator<Item = u32>
{
    let mut sum = 0;
    for integer in integers {
        sum += x;
    }
    sum
}
}

Intuitively, a function that has an argument of type impl Iterator is saying "you can give me any sort of iterator that you like".

Impl trait in argument types

stable

When you use an impl Trait in the type of a function argument, that is generally equivalent to adding a generic parameter to the function. So this function:


#![allow(unused)]
fn main() {
fn sum_integers(integers: impl Iterator<Item = u32>) -> u32 {
    //                    ^^^^^^^^^^^^^
    let mut sum = 0;
    for integer in integers {
        sum += x;
    }
    sum
}
}

is roughly equivalent to the following generic function:


#![allow(unused)]
fn main() {
fn sum_integers<I>(integers: I) -> u32
where
    I: Iterator<Item = u32>
{
    let mut sum = 0;
    for integer in integers {
        sum += x;
    }
    sum
}
}

Intuitively, a function that has an argument of type impl Iterator is saying "you can give me any sort of iterator that you like".

Turbofish

needs decision

Using impl Trait in argument position conceptually adds a generic parameter to the function. However, this generic parameter is different from explicit, named parameters. Its value cannot be specified explicitly using the turbofish operator, and must be inferred. Therefore, these two functions behave in slightly different ways:


#![allow(unused)]
fn main() {
fn foo1(x: impl Clone) { }
fn foo2<C: Clone>(x: C) { }
}

The difference is that one can write foo2::<i32> to manually specify the value of the C parameter, but the value of the impl Clone parameter on foo1 must be inferred from the argument types that are supplied (e.g, foo1(22)).

It is possible to mix explicit generics with impl Trait:


#![allow(unused)]
fn main() {
fn foo3<A: Debug>(x: A, b: impl Clone) { }
}

In this case, one can use turbofish to specify the value of A, but not the impl Clone. In other words, one could write foo3::<i32> to get a function where A is specified as i32, but the value of impl Clone will still have to be inferred from usage.

Implication. The fact that impl trait and explicit generics are not equivalent means that one cannot migrate between them in a semver-compliant way, although breakage is quite unusual in practice.

Implementation note: This section describes the "recommended" behavior that we expect to propose to the lang team. The currently implemented semantics are different. They forbid turbofish from being used on any function that employs argument position impl trait.

Traits and impls

stable

You can use impl Trait in argument position in traits and impls, but you must use it consistently in both. For example, the following is legal:


#![allow(unused)]
fn main() {
trait Operation {
    fn compute(x: impl Iterator<Item = u32>) -> u32;
}

struct Sum;
impl Operation for Sum {
    fn compute(x: impl Iterator<Item = u32>) -> u32 {
        x.sum()
    }
}
}

But the following would be illegal:


#![allow(unused)]
fn main() {
struct Max;
impl Operation for Max {
    fn compute<I: Iterator<Item = u32>>(x: I) -> u32 {
        x.max()
    }
}
}

Impl trait in type aliases

nightly

Rust has a concept of type aliases, which let you declare one type name as an alias for another:


#![allow(unused)]
fn main() {
type Integer = i32;
}

Type aliases can be useful for giving a short alias for some complex type. For example, imagine we had a module odd that defined an odd_integers function. odd_integers(x, y) returns an iterator over all the odd integers between x and y. Because the full return type is fairly complicated, the module defines a type alias OddIntegers that people can use to refer to it.


#![allow(unused)]
fn main() {
mod odd {
    pub type OddIntegers = std::iter::StepBy<std::ops::Range<u32>>;

    pub fn odd_integers(mut start: u32, stop: u32) -> OddIntegers {
        if (start % 2) == 0 {
            start += 1;
        }
        (start..stop).step_by(2)
    }
}
}

Of course, typing that long type is kind of annoying. Moreover, there are some types in Rust that don't have explicit names. For example, another way to write the "odd integers" iterator would be to call filter with a closure. But if you try to write out the type for that, you'll find that you need to give a name to the type representing the closure itself, which you can't do!


#![allow(unused)]
fn main() {
mod odd {
    pub type OddIntegers = std::iter::Filter<std::ops::Range<u32>, /* what goes here? */>;

    pub fn odd_integers(start: u32, stop: u32) -> OddIntegers {
        (start..stop).filter(|i| i % 2 != 0)
    }
}
}

Enter impl Trait1

1

"He says in parentheses"

The truth is that specifying the exact type for OddIntegers is overkill anyway. Chances are that we don't actually care what exact type is used there, we only care that odd_integers returns some kind of iterator. In fact, we might prefer not to specify it: that gives us the freedom to change how odd_integers is implemented in the future without potentially affecting our callers. If this sounds familiar, that's good -- it's exactly the kind of scenario that impl Trait is meant to solve!

Using impl Trait, we can still have a type alias OddIntegers, but we can avoid specifying exactly what its value is. Instead, we just say that it is "some iterator over u32":


#![allow(unused)]
fn main() {
mod odd {
    pub type OddIntegers = impl Iterator<Item = u32>;
    //                 ^^^^^^^^^^^^^^^^^^^^^^^^^ :tada:!

    pub fn odd_integers(mut start: u32, stop: u32) -> OddIntegers {
        if (start % 2) == 0 {
            start += 1;
        }
        (start..stop).step_by(2)
    }
}
}

Now it's the compiler's job to figure out the value of that type alias. In this case, the type for OddIntegers would be inferred to StepBy<Range<u32>>, but we could also change the definition to use filter, in which case the hidden type would be Filter<Range<u32>, C>, where C represents the type of the closure. This shows something important: the value of an impl Trait can include types that don't otherwise have names (in this case, the closure type C).

Hidden types

You can see that impl trait behaves a bit differently depending on where you use it. What all positions have in common is that they stand in for "some type that implements the given trait". We call that type the hidden type, because you generally don't get to rely on exactly what it is, you only get to rely on the bounds that it satisfies. What distinguishes the various positions where you can use impl Trait is which code determines the hidden type:

PositionWho determines the hidden type
Argument positionThe caller
Type aliasCode within the enclosing module
... (full table in Appendix B)

Inferring the hidden type

nightly

When you use impl Trait in a type alias, the hidden type is inferred based on the code in the enclosing module. We call this enclosing module the defining scope for the impl trait. In our example, the defining scope is the module odd:


#![allow(unused)]
fn main() {
mod odd {
    pub type OddIntegers = impl Iterator<Item = u32>;
    pub fn odd_integers(mut start: u32, stop: u32) -> OddIntegers { .. }
}
}

As we type-check the functions within the module odd (or its submodules), we look at the ways that they use the type OddIntegers. From this we can derive a set of constraints on what the hidden type has to be. For example, the compiler computes that odd_integers returns a value StepBy<Range<u32>>, but the function is declared to return OddIntegers: therefore we conclude that odd_integers requires the hidden type to be StepBy<Range<u32>>.

There can be multiple functions that constrain the type

Of course, modules can have more than one function! It's fine to have multiple functions that constrain the type OddIntegers, so long as they all ultimately constrain it to be the same thing. It's also possible to constrain OddIntegers by referencing it in other places. For example, consider the function another_function here:


#![allow(unused)]
fn main() {
mod odd {
    pub type OddIntegers = impl Iterator<Item = u32>;

    // Requires `OddIntegers` to be `StepBy<Range<u32>>`
    pub fn odd_integers(mut start: u32, stop: u32) -> OddIntegers {..}

    // Requires `OddIntegers` to be `StepBy<Range<u32>>`
    fn another_function() {
        let x: OddIntegers = (3..5).step_by(2);
    }
}
}

another_function references OddIntegers as the type of the variable x -- but the value assigned to x is also of type StepBy<Range<u32>>, so everything works.

Each function within the defining scope must specify the same type

These two functions each independently require that OddIntegers has the same underlying type. What is not ok is to have two functions that require different types:


#![allow(unused)]
fn main() {
type OddIntegers = impl Iterator<Item = u32>;
//                 ^^^^^^^^^^^^^^^^^^^^^^^^^ :tada:!

fn odd_integers(mut start: u32, stop: u32) -> OddIntegers {
    if (start % 2) == 0 {
        start += 1;
    }
    // Requires `OddIntegers` to be `StepBy<Range<u32>>`
    (start..stop).step_by(2)
}

fn another_function() {
    // Requires `OddIntegers` to be `Filter<...>`.
    //
    // ERROR!
    let x: OddIntegers = (3..5).filter(|x| x % 2 != 0);
}
}

Each function within the defining scope must specify the complete type

Similarly, it is not ok to have functions that only partially constrain and impl Trait. Consider this example:


#![allow(unused)]
fn main() {
type SomethingDebug = impl Debug;

fn something_debug() -> SomethingDebug {
    // Constrains SomethingDebug to `Option<_>`
    // (where `_` represents "some type yet to be inferred")
    None
}
}

Here, the something_debug function constrains SomethingDebug to be some sort of Option, but it doesn't say the full type. We only have Option<_>. That's not good enough. You could change this function to specify what kind of option it is, though, and that would be fine:


#![allow(unused)]
fn main() {
type SomethingDebug = impl Debug;

fn something_debug() -> SomethingDebug {
    // Constrains SomethingDebug to `Option<()>`
    None::<()>
}
}

Referencing the type alias outside of the module

nightly

Since it is declared to be public, the type alias OddIntegers can be referenced from outside the module odd. This allows callers to give a name to the return type of odd_integers:

mod odd {
    pub type OddIntegers = impl Iterator<Item = u32>;
    pub fn odd_integers(mut start: u32, stop: u32) -> OddIntegers { /* as above */ }
}

fn main() {
    let odds: odd::OddIntegers = odd::odd_integers(1, 10);
    for i in odds {
        println!("{}", i);
    }
}

Because that code is outside of the defining scope, however, it is not allowed to influence or observe the hidden type. It can only rely on the that were declared1 (e.g., Iterator<Item = u32>). For example, the following code would not type check:

1

With the exception of auto traits.

mod odd {
    pub type OddIntegers = impl Iterator<Item = u32>;
    pub fn odd_integers(mut start: u32, stop: u32) -> OddIntegers { /* as above */ }
}

fn main() {
    // Error!
    let odds: std::iter::StepBy<std::iter::Range<u32>> =
        odd::odd_integers(1, 10);
    for i in odds {
        println!("{}", i);
    }
}

This code fails because odds is not known to have that exact type -- main can only see that odds returns "some kind of iterator".

Generic parameters and type alias impl trait

![nightly][] [planning rfc]: https://img.shields.io/badge/status-draft%20rfc-informational [accepted rfc]: https://img.shields.io/badge/status-accepted%20rfc-informational [needs decision]: https://img.shields.io/badge/status-needs%20decision-informational [stable]: https://img.shields.io/badge/status-stable-success [nightly]: https://img.shields.io/badge/status-nightly-important [may-change]: https://img.shields.io/badge/warning-implementation%20may%20change-critical

Type alias impl Traits can also be generic:


#![allow(unused)]
fn main() {
type SomeTupleIterator<I, J> = impl Iterator<Item = (I, J)>;
}

The hidden type inferred for an impl trait that appears in a type alias may always reference any of the generic parameters from that type alias.

Limits on the generic arguments to a type alias impl trait

References to a type aliases that contain an impl trait, however, are subject to some restrictions. In particular, those type aliases can only be used with other generic types that are in scope, and each parameter must be distinct. Example:


#![allow(unused)]
fn main() {
fn foo1<A, B>() -> SomeTupleIterator<A, B> { /* ok */ }
fn foo2<A, B>() -> SomeTupleIterator<B, A> { /* ok */ }
fn foo3<A, B>() -> SomeTupleIterator<A, A> { /* not ok -- same parameter used twice */ }
fn foo4<A, B>() -> SomeTupleIterator<A, u32> { /* not ok -- u32 is not a type parameter */ }
}

These rules ensure that inference is tractable. Consider the case of -> SomeTupleIterator<A, u32>. Imagine that foo4 returned an iterator over (A, u32) tuples. How do we translate that value to the hidden type for SomeTupleIterator? There would actually be multiple possibilities. Either it would be an iterator over (I, J) (with I = A and J = u32 in this case), or an iterator over (I, u32) (with I = A and J unused).

Impl trait in return types

stable

In the section on type aliases, we gave the example of a function odd_integers that returned a type alias OddIntegers. If you prefer, you can forego defining the type alias and simply return an impl Trait directly:


#![allow(unused)]
fn main() {
fn odd_integers(start: u32, stop: u32) -> impl Iterator<Item = u32> {
    (start..stop).filter(|i| i % 2 != 0)
}
}

This is almost equivalent to the type alias we saw before, but there are two differences:

  • The defining scope for the impl trait is just the function odd_integers, and not the enclosing module.
    • This means that other functions within the same module cannot observe or constrain the hidden type.
  • There is no direct way to name the resulting type (because you didn't define a type alias).

Generic parameter capture

stable

When you use an impl trait in return position, the hidden type may make use of any of the type parameters, and hence the following function is legal:


#![allow(unused)]
fn main() {
// Hidden type: Option<T>, which references T
fn foo<T: Clone>(t: T) -> impl Clone {
    Some(t)
}
}

However, it may not reference lifetime parameters unless those lifetime parameters appear in the impl trait bounds. The following


#![allow(unused)]
fn main() {
// Error: hidden type `Option<&'a u32>` references `'a`
fn foo<'a>(t: &'a u32) -> impl Clone {
    Some(t)
}
}

XXX document:

  • you can capture type parameters
  • but not lifetimes, unless they appear in the bounds

Naming impl trait in return types

planning rfc

Return position impl Trait introduces types that do not have proper names. If you find yourself frequently giving that type a name, your best bet is to introduce a type alias. However, in a pinch, it is possible to access those types by getting the type for the surrounding function and extracting its FnOnce::Output associated type. Given a function like...


#![allow(unused)]
fn main() {
fn make_iter() -> impl Iterator<Item = u32> {
    0 .. 100
}
}

...one could use the type make_iter::Output to access the inferred impl Iterator (which will be a Range<u32>, in this case).

Function types can generally be named via their path:

  • For function items, simply the name of the function (fn_name)
  • For inherent methods, the fully qualified syntax of Type::fn_name
  • For trait methods defined in an impl, use the fully qualified syntax <Type as Trait>::fn_name (as if fn_name were an associated type)

In each case, the type for the function is a zero-sized type that implements the Fn, FnMut, and FnOnce traits. This type is considered to be defined in its surrounding module and it is also possible to use it in other contexts, such as to implement the Default trait:


#![allow(unused)]
fn main() {
fn foo() -> u32 { 22 }

impl Default for foo {
    fn default() -> Self {
        foo
    }
}
}

Return types in trait definitions and impls

planning rfc

When you use impl Trait as the return type for a function within a trait definition or trait impl, the intent is the same: impls that implement this trait return "some type that implements Trait", and users of the trait can only rely on that. However, the desugaring to achieve that effect looks somewhat different than other cases of impl trait in return position. This is because we cannot desugar to a type alias in the surrounding module; we need to desugar to an associated type (effectively, a type alias in the trait).

Consider the following trait:


#![allow(unused)]
fn main() {
trait IntoIntIterator {
    fn into_int_iter(self) -> impl Iterator<Item = u32>;
}
}

The semantics of this are analogous to introducing a new associated type within the surrounding trait;


#![allow(unused)]
fn main() {
trait IntoIntIterator { // desugared
    type IntoIntIter: Iterator<Item = u32>;
    fn into_int_iter(self) -> Self::IntoIntIter;
}
}

(In general, this associated type may be generic; it would contain whatever generic parameters are captured per the generic capture rules given previously.)

This associated type is introduced by the compiler and cannot be named by users.

The impl for a trait like IntoIntIterator must also use impl Trait in return position:


#![allow(unused)]
fn main() {
impl IntoIntIterator for Vec<u32> {
    fn into_int_iter(self) -> impl Iterator<Item = u32> {
        self.into_iter()
    }
}
}

This is equivalent to specify the value of the associated type as an impl Trait:


#![allow(unused)]
fn main() {
impl IntoIntIterator for Vec<u32> {
    type IntoIntIter = impl Iterator<Item = u32>
    fn into_int_iter(self) -> Self::IntoIntIter {
        self.into_iter()
    }
}
}

Impl trait in let bindings

accepted rfc

You can also use impl Trait in the type of a local variable:


#![allow(unused)]
fn main() {
let x: impl Clone = 22_i32;
}

This is equivalent to introducing a type alias impl trait with the scope of the enclosing function:


#![allow(unused)]
fn main() {
type X = impl Clone;
let x: X = 22_i32;
}

Auto traits and impl trait

stable

When talking about output impl Traits in the previous section, we said that callers cannot rely on the precise hidden type, but must instead rely only on the declared bounds from the impl Trait. This was actually a simplification: in reality, callers are able to rely on some aspects of the hidden type. Specifically, they are able to deduce whether the hidden type implements the various auto traits, like Send and Sync:


#![allow(unused)]
fn main() {
fn odd_integers(start: u32, stop: u32) -> impl Iterator<Item = u32> {
    (start..stop).filter(|i| i % 2 != 0)
}

fn other_function() {
    let integers = odd_integers();

    // This requires that `integers` is `Send`.
    // The compiler "peeks" at the hidden type
    // to check that this is true.
    std::thread::spawn(move || {
        for integer in integers {
            println!("{}", integer);
        }
    }).join();
}
}

The motivation behind auto trait leakage is that it makes working with impl Trait as convenient as other kinds of "newtype wrappers" when it comes to threading. For example, if you were to replace the odd_integers return type with a newtype OddIntegers that hides the iterator type (as is common practice)...


#![allow(unused)]
fn main() {
struct OddIntegers {
    iterator: Filter<Range<...>, ...>
}

fn odd_integers(start: u32, stop: u32) -> OddIntegers { ... }
}

...you would find that OddIntegers implements Send if that hidden type (that appears in its private field) implements Send. (In this particular case, it would be challenging to create a struct because it would have to refer to the returned closure type, which is anonymous.)

Appendix A: Inference details

nightly

Summary

The process to infer the hidden type for an impl Trait is as follows.

  • When type-checking functions or code within the defining scope of an impl Trait X:
    • Accumulate subtyping constraints from type check:
      • For example, let var: X = 22_i32 would create the subtyping constraint that i32 is a subtype of X.
      • Similarly, given fn foo() -> X, let var: i32 = foo() wold create the subtyping constraint that X be a subtype of i32.
    • The type X is assumed to implement the traits that appear in its bounds
      • Exception: auto traits. Proving an auto trait X: Send is done by "revealing" the hidden type and proving the auto trait against the hidden type.

Terminology: Opaque type

An impl trait in output position is called an opaque type. We write the opaque type as O<P0...Pn>, where P0...Pn are the generic types on the opaque type. For a type alias impl trait, the generic types are the generics from the type alias. For a return position impl trait, the generics are defined by the RPIT capture rules.

Terminology: Opaque type bounds

Given an opaque type O<P1...Pn>, let B1..Bn be the bounds on that opaque type, appropriately substituted for P1..Pn. For example, if the user wrote type Foo<T> = impl PartialEq<T> + Debug + 'static and we have the opaque type Foo<u32>, then the bounds would be PartialEq<u32>, Debug, and 'static.

Notation: applying bounds to a type to create a where clause

Given a type T and an opaque type bound B1, we write T: B1 to signify the where clause that results from using T as the "self type" for the bound B1. For example, given the type i32 and the bound PartialEq<u32>, the where clause would be i32: PartialEq<u32> Given the type i32 and the bound 'static, the where clause would be i32: 'static.

Type checking opaque types

The following section describes how to type check functions or other pieces of code that reference opaque types.

Rules that always apply

The following rules for type-checking opaque types apply to all code, whether or not it is part of the type's defining scope.

Reflexive equality for opaque types

Like any type, an opaque type O<P0...Pn> can be judged to be equal to itself. This does not require knowing the hidden type.

Example. The following program compiles because of this rule.

mod foo {
    type Foo = impl Clone;

    fn make_foo(x: u32) -> Foo {
        x
    }
}

fn main() {
    let mut x: Foo = make_foo(22);

    // Requires knowing that `Foo = Foo`.
    x = make_foo(44);
}

Example. The following program does not compile even though the hidden types are the same.

mod foo {
    type Foo<T: Clone> = impl Clone;

    fn make_foo<A>(x: u32) -> Foo<A> {
        x
    }
}

fn main() {
    let mut x: Foo<String> = make_foo::<String>(22);

    // `Foo<String>` is not assumed to be equal to `Foo<char>`.
    x = make_foo::<char>(44);
}

Method dispatch and trait matching

Given a method invocation o.m(...) where the method receiver o has opaque type O<P1...Pn>, methods defined in the opaque type's bounds are considered "inherent methods", similar to a dyn receiver or a generic type.

Similarly, the compiler can assume that O<P1...Pn>: Bi is true for any of the declared bounds Bi.

Example. The following program compiles because of this rule.


#![allow(unused)]
fn main() {
type Foo = impl Clone;

fn clone_foo(f: Foo) -> Foo {
    // Clone can be called without knowing the hidden type:
    f.clone()
}

fn make_foo() -> Foo {
    // Hidden type constrained to be `u32`
    22_u32
}
}

Example. The following program does not compile even though the hidden type for Foo is known within clone_foo; this is because the type of f is impl Clone and we interpret that as a wish to only rely on the fact that Foo: Clone and nothing else (modulo auto trait leakage, see next rule).


#![allow(unused)]
fn main() {
type Foo = impl Clone;

fn clone_foo() {
    let f: Foo = 22_u32;

    // We don't know that `Foo: Debug`.
    debug!("{:?}", f);
}
}

Rules that only apply outside of the defining scope: auto trait leakage

The following type-checking rules can only be used when type-checking an item I that is not within the defining scope for the opaque type. When the type-checker for I wishes to employ one of these rules, it needs to "fetch" the hidden type for O. This may create a cycle in the computation graph if determining the hidden type for O requires type-checking the body of I (e.g., to compute the hidden type for another opaque type O1); cyclic cases result in a compilation error.

Example. The following program compiles because of this rule:


#![allow(unused)]
fn main() {
type Foo = impl Clone;

fn is_send<T: Send>() {}

fn clone_foo() {
    let f: Foo = 22_u32;

    // We can leak the hidden type of `u32` defined within this function.
    is_send::<Foo>();
}
}

Example. The following program does not compile because of a cycle:


#![allow(unused)]
fn main() {
fn is_send<T: Send>() { }        

mod foo {
    pub type Foo = impl Clone;

    pub fn make_foo() -> Foo {
        // Requires knowing hidden type of `Bar`:
        is_send::<crate::bar::Bar>();
        22_u32
    }
}

mod bar {
    pub type Bar = impl Clone;

    pub fn make_bar() -> Foo {
        // Requires knowing hidden type of `Foo`:
        is_send::<crate::foo::Foo>();
        22_u32
    }
}
}

Auto trait leakage

When proving O<P1...Pn>: AT for some auto trait AT, any item is permitted to use the hidden type H for O<P1...Pn> and show that H: AT.

Rules that only apply within the defining scope: proposing an opaque type

The following type-checking rules can only be used when type-checking an item I that is within the defining scope for the opaque type O. If any of these rules are needed to type-check I, then I must compute a consistent type H to use as the hidden type for O. Any item I that relies on the rules in this section is said to propose the hidden type H for the opaque type O. The process for inferring a hidden type is described below.

Equality between an opaque type and its hidden type

An opaque type O<P0...Pn> can be judged to be equal to its hidden type H.

Example. The following program compiles because of this rule:


#![allow(unused)]
fn main() {
type Foo = impl Clone;

fn test() {
    // Requires that `Foo` be equal to the hidden type, `u32`.
    let x: Foo = 22_u32;
}
}

Auto trait leakage

When proving O<P1...Pn>: AT for some auto trait AT, I can use the hidden type H to show that H: AT.

Example. The following program compiles because of this rule:


#![allow(unused)]
fn main() {
type Foo = impl Clone;

fn is_send<T: Send>() {}

fn test() {
    let x: Foo = 22_u32;
    is_send::<Foo>();
}
}

Example. The following program does not compile. This is because it requires this rule to compile, but does not actually constrain the hidden type in any other way.


#![allow(unused)]
fn main() {
type Foo = impl Clone;

fn is_send<T: Send>() {}

fn test() {
    is_send::<Foo>();
}
}

Determining the hidden type

To determine the hidden type for some opaque type O, we examine the set C of items that propose a hidden type O. If that set is an empty set, there is a compilation error. If two items in the set propose distinct hidden types for O, that is also an error. Otherwise, if all items propose the same hidden type H for O, then H becomes the hidden type for O.

Computing the hidden type proposed by an item I

Computing the hidden type H for O<P1..Pn> that is required by the item I must be done independently from any other items within the defining scope of O. It can be done by creating an inference variable ?V for O<P1..Pn> and unifying ?O with all types that must be equal to O<P1...Pn>. This inference variable ?V is called the exemplar, as it is an "example" of what the hidden type is when P1..Pn are substituted for the generic arguments of O. Note that a given function may produce multiple exemplars for a single opaque type if it contains multiple references to O with distinct generic arguments. Computing the actual hidden type is done by higher-order pattern unification.

Checking the hidden type proposed by an item I

Once the hidden type for an item I is determined, we also have to check that the hidden type is well-formed in the context of the type alias and that its bounds are satisfied. Since the where clauses that were in scope when the exemplar was computed can be different from those declared on the opaque type, this is not a given.

Example. The following program does not compile because of this check. Here, the exemplar is T and the hidden type is X. However, the type alias does not declare that X: Clone, so the impl Clone bounds are not known to be satisfied.


#![allow(unused)]
fn main() {
type Foo<X> = impl Clone;

fn make_foo<T: Clone>(t: T) {
    t
}
}

Limitations on exemplars

Whenever an item I proposes a hidden type, the following conditions must be met:

  • All of the exemplars from a given item I must map to the same hidden type H.
  • Each exemplar type E must be completely constrained and must not involve unbound inference variables.

Examples

The following function has two exemplars but they both map to the same hidden type, so it is legal:


#![allow(unused)]
fn main() {
type Foo<T, U> = impl Debug;

fn compatible<A, B>(a: A, b: B) {
    // Exemplar: (A, B) for Foo<A, B>
    // Hidden type: (T, U)
    let _: Foo<A, B> = (a, b);

    // Exemplar: (B, A) for Foo<B, A>
    // Hidden type: (T, U)
    let _: Foo<B, A> = (b, a);
}
}

The following program is illegal because of the restriction against having incompatible examplars.


#![allow(unused)]
fn main() {
type Foo<T, U> = impl Debug;

fn incompatible<A, B>(a: A, b: B) {
    // Exemplar: (A, B)
    // Hidden type: (T, U)
    let _: Foo<A, B> = (a, b);

    // Exemplar: (B, A)
    // Hidden type: (U, T)
    let _: Foo<A, B> = (b, a);
}
}

The following program is illegal because of the restriction against having incomplete types. This is true even though the two functions, taken in aggregate, have enough information to compute the hidden type for Foo.


#![allow(unused)]
fn main() {
type Foo = impl Debug;

fn incomplete1() {
    // Computes an incomplete exemplar of `Result<(), _>`.
    let x: Foo = Ok(());
}

fn incomplete2() {
    // Computes an incomplete exemplar of `Result<_, ()>`.
    let x: Foo = Err(());
}
}

Higher-order pattern unification

When an item I finishes its type check, it will have computed an exemplar type E for the opaque type O<P1..Pn>. This must be mapped back to the hidden type H. Thanks to the limitations on type alias generic parameters, this can be done by a process called higher-order pattern unification (a limited, tractable form of higher-order unification). Higher-order pattern unification is best explained by example.

Consider an opaque type Foo:


#![allow(unused)]
fn main() {
type Foo<T: Clone, U: Clone> = impl Clone;
}

and an item make_foo that contains Foo:


#![allow(unused)]
fn main() {
fn make_foo<X: Clone, Y: Clone>(x: X, y: Y) -> Foo<X, Y> {
    vec![(x, y)]
}
}

Here the opaque type O<P1, P2> is Foo<X, Y> (i.e., P1 is X and P2 is Y) and the exemplar type E is Vec<(X, Y)>. We wish to compute the hidden type, which will be Vec<(T, U)> -- note that the hidden type references the generics from the declaration of Foo, whereas the exemplar references the generics from make_foo. Computing the hidden type can be done by finding each occurence of a generic argument Pi (in this case, X and Y) and mapping to the corresponding generic parameter i from the opaque type declaration (in this case, T and U respectively).

This process is well-defined because of the limitations on type alias generic parameters. Thanks to those limitations, we know that:

  • each generic argument Pi will be some generic type on make_foo;
  • each generic argument Pi to the opaque type will be distinct from each other argument Pj.

In particular, these limitations mean that whenever we see an instance of some parameter Pi in the exemplar, the only way that Pi could appear in the final hidden type is if it was introduced by substitution for one of the generic arguments.

To see the ambiguity that is introduced without these limitations, consider what would mappen if you had a function like bad_make_foo1:


#![allow(unused)]
fn main() {
fn bad_make_foo1() -> Foo<u32, i32> {
    vec![(22_u32, 22_i32)]
}
}

Here the exemplar type is Vec<(u32, i32)>, but the hidden type is ambiguous. It could be Vec<(T, U)>, as before, but it could also just be Vec<(u32, i32)>. The problem is that this example violates the first restriction: the parameters u32 and i32 are not generics on bad_make_foo1. This means that they are types which can be named from both the definition of type Foo and from within the scope of bad_make_foo1, introducing ambiguity.

A similar problem happens when type parameters are repeated, as illustrated by bad_make_foo2:


#![allow(unused)]
fn main() {
fn bad_make_foo2<X: Clone>(x: X) -> Foo<X, X> {
    vec![(x.clone(), x.clone())]
}
}

The exemplar type here is Vec<(X, X)>. The hidden type, however, could either be Vec<(T, T)> or Vec<(T, U)> or Vec<(U, U)>. All of them would be the same after substitution.

Appendix B: Where can impl trait be used

Overview

Impl trait is accepted in the following locations:

PositionWho determines the hidden typeRoleStatus
Argument positionEach callerInputstable
Type aliasCode within the enclosing moduleOutputnightly
Return position, free fnsThe function bodyOutputstable
Return position, inherent implsThe function bodyOutputstable
Return position, trait implsThe function bodyOutputplanning rfc
Return position, traitsThe implInputplanning rfc
Let bindingThe enclosing function or code blockOutputaccepted rfc
Const bindingThe const initializerOutputaccepted rfc
Static bindingThe static initializerOutputaccepted rfc

Impl trait is not accepted in other locations; Appendix C covers some of the locations where impl Trait is not presently accepted and why.

Role: Input vs output

Impl traits in general play one of two roles.

Input impl trait

An input impl trait corresponds loosely to a generic parameter. The code that references the impl Trait may be instantiated multiple times with different values. For example, a function using impl Trait in argument position can be called with many different types for the impl Trait.

Output impl trait

An output impl trait plays a role similar to a type that is given by the user. In this case, the impl trait represents a single type (although that type may be relative to generic types are in scope) which is inferred from the code around the definition. For example, a free function with an impl Trait in return position will have the true return type inferred from the function body. The type represented by an output impl trait is called the hidden type. The code that is used to infer the value for an output impl trait is called its defining scope.

General rules for "input" vs "output"

In general, the role of impl Trait and '_ both follow the same rules in terms of being "input vs output". When in an argument listing, that is an "input" role and they correspond to a fresh parameter in the innermost binder. Otherwise, they are in "output" role and the corresponding to something which is inferred or selected from context (in the case of '_ in return position, it is selected based on the rules of lifetime elision; '_ within a function body corresponds to inference).

Type alias impl trait


#![allow(unused)]
fn main() {
type Foo = impl Trait;
}

Creates a opaque type whose value will be inferred by the contents of the enclosing module (and its submodules).

Fn argument position


#![allow(unused)]
fn main() {
fn foo(x: impl Trait)
}

becomes an "anonymous" generic parameter, analogous to


#![allow(unused)]
fn main() {
fn foo<T: Trait>(x: T)
}

However, when impl Trait is used on a function, the resulting type parameter cannot be specified using "turbofish" form; its value must be inferred. (status: this detail not yet decided).

Places this can be used:

  • Top-level functions and inherent methods
  • Trait methods
    • Implication: trait is not dyn safe

Fn return position


#![allow(unused)]
fn main() {
fn foo() -> impl Trait)
}

becomes an "anonymous" generic parameter, analogous to


#![allow(unused)]
fn main() {
fn foo<T: Trait>(x: T)

type Foo = impl Trait; // defining scope: just the fn
fn foo() -> Foo
}

Places this can be used:

  • Top-level functions and inherent methods
  • Trait methods (pending; no RFC)

Let bindings


#![allow(unused)]
fn main() {
fn foo() {
    let x: impl Debug = ...;
}
}

becomes a type alias impl trait, analogous to


#![allow(unused)]
fn main() {
type Foo = impl Debug; // scope: fn body
fn foo() {
    let x: Foo = ...;
}
}

Appendix C: Where can impl trait NOT be used

Where is impl Trait not (yet?) accepted and why.

dyn Types, angle brackets


#![allow(unused)]
fn main() {
fn foo(x: &mut dyn Iterator<Item = impl Debug>)
}

dyn Types, parentheses


#![allow(unused)]
fn main() {
fn foo(x: &mut dyn FnMut(impl Debug))
}

Unclear whether this should (eventually) be short for dyn for<T: Debug> FnMut(T) (which would not be legal) to stay analogous to impl FnOnce(impl Debug).

dyn Types, return types


#![allow(unused)]
fn main() {
fn foo(x: &mut dyn FnMut() -> impl Debug)
}

Nested impl trait, parentheses


#![allow(unused)]
fn main() {
fn foo(x: impl FnMut(impl Debug))
}

Unclear whether this should (eventually) be short for impl for<T: Debug> FnMut(T) or some other notation.

Where clauses, angle brackets


#![allow(unused)]
fn main() {
fn foo()
where T: PartialEq<impl Clone>
}

Where clauses, parentheses


#![allow(unused)]
fn main() {
fn foo()
where T: FnOnce(impl Clone)
}

Where clauses, return position


#![allow(unused)]
fn main() {
fn foo()
where T: FnOnce() -> impl Clone
}

Struct fields


#![allow(unused)]
fn main() {
struct Foo {
    x: impl Debug
}
}

It would be plausible to accept this as equivalent to a type alias at module scope, which seems reasonable, but it is easy to write that explicitly, and there may be future meanings that are more interesting (such as an existential type local to the struct).

It is not possible to accept this in input position, because that would imply that the type Foo has more generic parameters than are written on the type itself. Unlike with functions, where the values of those generic parameters can be inferred from the arguments, structs can appear in many contexts where inferring the values of those generic types would not be tractable.

Appendix D: Glossary

See the list of terms in the sidebar.

Defining scope

The defining scope of an impl Trait is the code that is used to infer its hidden type. See the explanation of type alias impl trait for more details.

Hidden type

The hidden type of an impl Trait is the underlying type that the impl Trait represents. The compiler always infers the hidden type for an impl trait from the rest of the code. In some cases, there is just a single hidden type, but in other cases -- notably argument position impl trait -- there can be multiple hidden types (e.g., one per call site).

Input impl trait

An impl Trait that can be instantiated multiple times with different values, similar to a generic parameter. See Appendix B for more details.

Output impl trait

An impl Trait that corresponds to a single, hidden type that is inferred from the surrounding code. See Appendix B for more details.

✨ RFCs

The design for impl trait has been defined by a number of RFCs and decisions. This repository represents the "accumulated state" of all these RFCs, and also includes drafts of new RFCs that are in the works. Sometimes, though, it is useful to return to an RFC to see the details. Here is a comprehensive listing of the impl trait RFCs along with some of the core ideas they describe. Please keep in mind that details of the implementation or design may have been amended since the RFC was accepted, however.

Accepted RFCs

Draft RFCs

Return position impl Trait in traits

This is a draft RFC that will be submitted to the rust-lang/rfcs repository when it is ready.

Feedback welcome!


Return position impl Trait in traits

Summary

  • Permit impl Trait in fn return position within traits and trait impls.
  • This desugars to an anonymous associated type.

Motivation

The impl Trait syntax is currently accepted in a variety of places within the Rust language to mean "some type that implements Trait" (for an overview, see the explainer from the impl trait initiative). For function arguments, impl Trait is equivalent to a generic parameter and it is accepted in all kinds of functions (free functions, inherent impls, traits, and trait impls). In return position, impl Trait corresponds to an opaque type whose value is inferred. In that role, it is currently accepted only in free functions and inherent impls. This RFC extends the support to cover traits and trait impls, just like argument position.

Example use case

The use case for -> impl Trait in trait fns is similar to its use in other contexts: traits often wish to return "some type" without specifying the exact type. As a simple example that we will use through the RFC, consider the NewIntoIterator trait, which is a variant of the existing IntoIterator that uses impl Iterator as the return type:


#![allow(unused)]
fn main() {
trait NewIntoIterator {
    type Item;
    fn into_iter(self) -> impl Iterator<Item = Self::Item>;
}
}

Guide-level explanation

This section assumes familiarity with the basic semantics of impl trait in return position.

When you use impl Trait as the return type for a function within a trait definition or trait impl, the intent is the same: impls that implement this trait return "some type that implements Trait", and users of the trait can only rely on that. However, the desugaring to achieve that effect looks somewhat different than other cases of impl trait in return position. This is because we cannot desugar to a type alias in the surrounding module; we need to desugar to an associated type (effectively, a type alias in the trait).

Consider the following trait:


#![allow(unused)]
fn main() {
trait IntoIntIterator {
    fn into_int_iter(self) -> impl Iterator<Item = u32>;
}
}

The semantics of this are analogous to introducing a new associated type within the surrounding trait;


#![allow(unused)]
fn main() {
trait IntoIntIterator { // desugared
    type IntoIntIter: Iterator<Item = u32>;
    fn into_int_iter(self) -> Self::IntoIntIter;
}
}

(In general, this associated type may be generic; it would contain whatever generic parameters are captured per the generic capture rules given previously.)

This associated type is introduced by the compiler and cannot be named by users.

The impl for a trait like IntoIntIterator must also use impl Trait in return position:


#![allow(unused)]
fn main() {
impl IntoIntIterator for Vec<u32> {
    fn into_int_iter(self) -> impl Iterator<Item = u32> {
        self.into_iter()
    }
}
}

This is equivalent to specify the value of the associated type as an impl Trait:


#![allow(unused)]
fn main() {
impl IntoIntIterator for Vec<u32> {
    type IntoIntIter = impl Iterator<Item = u32>
    fn into_int_iter(self) -> Self::IntoIntIter {
        self.into_iter()
    }
}
}

Reference-level explanation

Equivalent desugaring for traits

Each -> impl Trait notation appearing in a trait fn return type is desugared to an anonymous associated type; the name of this type is a fresh name that cannot be typed by Rust programmers. In this RFC, we will use the name $ when illustrating desugarings and the like.

As a simple example, consider the following (more complex examples follow):


#![allow(unused)]
fn main() {
trait NewIntoIterator {
    type Item;
    fn into_iter(self) -> impl Iterator<Item = Self::Item>;
}

// becomes

trait NewIntoIterator {
    type Item;

    type $: Iterator<Item = Self::Item>;

    fn into_iter(self) -> <Self as NewIntoIterator>::$;
}
}

Equivalent desugaring for trait impls

Each -> impl Trait notation appearing in a trait impl fn return type is desugared to the same anonymous associated type $ defined in the trait along with a function that returns it. The value of this associated type $ is an impl Trait.


#![allow(unused)]
fn main() {
impl NewIntoIterator for Vec<u32> {
    type Item = u32;

    fn into_iter(self) -> impl Iterator<Item = Self::Item> {
        self.into_iter()
    }
}

// becomes

impl NewIntoIterator for Vec<u32> {
    type Item = u32;
    
    type $ = impl Iterator<Item = Self::Item>;

    fn into_iter(self) -> <Self as NewIntoIterator>::$ {
        self.into_iter()
    }
}
}

Impl trait must be used in both trait and trait impls

Using -> impl Trait notation in a trait requires that all trait impls also use -> impl Trait notation in their return types. Similarly, using -> impl Trait notation in an impl is only legal if the trait also uses that notation:


#![allow(unused)]
fn main() {
trait NewIntoIterator {
    type Item;
    fn into_iter(self) -> impl Iterator<Item = Self::Item>;
}

// OK:
impl NewIntoIterator for Vec<u32> {
    type Item = u32;
    fn into_iter(self) -> impl Iterator<Item = u32> {
        self.into_iter()
    }
}

// Not OK:
impl NewIntoIterator for Vec<u32> {
    type Item = u32;
    fn into_iter(self) -> vec::IntoIter<u32> {
        self.into_iter()
    }
}
}

Rationale: Maximizing forwards compatibility. We may wish at some point to permit impls and traits to diverge but there is no reason to do it at this time.

Generic parameter capture and GATs

As with -> impl Trait in other kinds of functions, the hidden type for -> impl Trait in a trait may reference any of the type or const parameters declared on the impl or the method; it may also reference any lifetime parameters that explicitly appear in the trait bounds (details). We say that a generic parameter is captured if it may appear in the hidden type.

When desugaring, captured parameters from the method are reflected as generic parameters on the $ associated type. Furthermore, the $ associated type has the required brings whatever where clauses are declared on the method into scope (excepting those which reference other parameters that are not captured). This transformation is precisely the same as the one which is applied to other forms of -> impl Trait, except that it applies to an associated type and not a top-level type alias.

Example:


#![allow(unused)]
fn main() {
trait RefIterator for Vec<u32> {
    type Item<'me>
    where 
        Self: 'me;

    fn iter<'a>(&'a self) -> impl Iterator<Item = Self:Item<'a>>;
}

// Since 'a is named in the bounds, it is captured.
// `RefIterator` thus becomes:

trait RefIterator for Vec<u32> {
    type Item<'me>
    where 
        Self: 'me;

    type $<'a>: impl Iterator<Item = Self::Item<'a>>
    where 
        Self: 'a; // Implied bound from fn

    fn iter<'a>(&'a self) -> Self::$<'a>;
}
}

Dyn safety

To start, traits that use -> impl Trait will not be considered dyn safe, even if the method has a where Self: Sized bound. This is because dyn types currently require that all associated types are named, and the $ type cannot be named. The other reason is that the value of impl Trait is often a type that is unique to a specific impl, so even if the $ type could be named, specifying its value would defeat the purpose of the dyn type, since it would effectively identify the dynamic type.

Rationale and alternatives

Can traits migrate from a named associated type to impl Trait?

Not compatibly, no, because they would no longer have a named associated type.

Can traits migrate from impl Trait to a named associated type?

Generally yes, but all impls would have to be rewritten.

Would there be any way to make it possible to migrate from impl Trait to a named associated type compatibly?

Potentially! There have been proposals to allow the values of associated types that appear in function return types to be inferred from the function declaration. So the trait has fn method(&self) -> Self::Iter and the impl has fn method(&self) -> impl Iterator, then the impl would also be inferred to have type Iter = impl Iterator (and the return type rewritten to reference it). This may be a good idea, but it is not proposed as part of this RFC.

What about using a named associated type?

One alternative under consideration was to use a named associated type instead of the anonymous $ type. The name could be derived by converting "snake case" methods to "camel case", for example. This has the advantage that users of the trait can refer to the return type by name.

We decided against this proposal:

  • Introducing a name by converting to camel-case feels surprising and inelegant.
  • Return position impl Trait in other kinds of functions doesn't introduce any sort of name for the return type, so it is not analogous.

There is a need to introduce a mechanism for naming the return type for functions that use -> impl Trait; we plan to introduce a second RFC addressing this need uniformly across all kinds of functions.

As a backwards compatibility note, named associated types could likely be introduced later, although there is always the possibility of users having introduced associated types with the same name.

Does auto trait leakage still occur for -> impl Trait in traits?

Yes, so long as the compiler has enough type information to figure out which impl you are using. In other words, given a trait function SomeTrait::foo, if you invoke a function <T as SomeTrait>::foo() where the self type is some generic parameter T, then the compiler doesn't really know what impl is being used, so no auto trait leakage can occur. But if you were to invoke <u32 as SomeTrait>::foo(), then the compiler could resolve to a specific impl, and hence a specific impl trait type alias, and auto trait leakage would occur as normal.

Would introducing a named associated type be a breaking change for a trait?

Converting from returning impl trait to an explicit associated type is a breaking change for impls of the trait. Given this code:


#![allow(unused)]
fn main() {
trait Foo {
    fn bar() -> impl Display;
}

impl Foo for u32 {
    fn bar() -> impl Display {
        self
    }
}
}

transforming it to the following:


#![allow(unused)]
fn main() {
trait Foo {
    type Bar: Display;
    fn bar() -> Self::Bar;
}

impl Foo for u32 {
    fn bar() -> impl Display {
        self
    }
}
}

Results in an impl in the impl Foo for u32. The Future possibilities section discusses some possible ways we could mitigate this in the future by other language extensions.

Prior art

There are a number of crates that do desugaring like this manually or with procedural macros. One notable example is real-async-trait.

Unresolved questions

  • None.

Future possibilities

We expect to introduce a mechanism for naming the result of -> impl Trait return types in a follow-up RFC (see the draft named function types rfc for the current thinking).

Similarly, we expect to be introducing language extensions to address the inability to use -> impl Trait types with dynamic dispatch. These mechanisms are needed for async fn as well. A good writeup of the challenges can be found on the "challenges" page of the async fundamentals initiative.

Finally, it would be possible to introduce a mechanism that allows users to give a name to the associated type that is returned by impl trait. One proposed mechanim is to support an inference mechanism, so that one if you have a function fn foo() -> Self::Foo that returns an associated type, the impl only needs to implement the function, and the compiler infers the value of Foo from the return type. Another options would be to extend the impl trait syntax generally to let uses give a name to the type alias or parameter that is introduced.

Draft RFC: Named function types

This is a draft RFC that will be submitted to the rust-lang/rfcs repository when it is ready.

Feedback welcome!


Summary

  • Given a function fn foo, introduce a type named fn#foo to represent the zero-sized type of the function ("function def").
    • When possible, a type named foo is also introduced, unless another type with that name already exists.
  • The generic parameters of a "function def" type are defined to include only named parameters. Anonymous parameters that appear in the argument are excluded:
    • Elided lifetimes in argument position are excluded.
    • impl Trait in argument position are excluded.
    • This implies that turbofish with a "function def" type cannot name the impl Trait arguments.
    • As a temporary measure, "function def" types that have named, late-bound lifetimes cannot accept lifetime parameters.
      • This rule already exists today.

Motivation

Problems we are solving

This RFC proposes two related changes that, together, work to close two remaining major "holes" in the impl Trait story:

  • How to name the return type of a function
  • How turbofish interacts with impl Trait in argument position (explainer)

These two problems at first seem orthogonal, but they turn out to be related. Let's first introduce the two problems.

Naming the return type of a function

"Return position" impl Trait (RPIT) generally refers to an "anonymous" opaque type whose value is inferred by the compiler:


#![allow(unused)]
fn main() {
fn odd_integers(start: u32, stop: u32) -> impl Iterator<Item = u32> {
    (start..stop).filter(|i| i % 2 == 0)
}

// becomes something like:

type OddIntegers = impl Iterator<Item = u32>;
fn odd_integers(start: u32, stop: u32) -> OddIntegers {
    (start..stop).filter(|i| i % 2 == 0)
}
}

When using RPIT in traits, this anonymous type is a kind of associated type:


#![allow(unused)]
fn main() {
trait IntoIntIterator {
    fn into_int_iter(self) -> impl Iterator<Item = u32>;
}

// becomes

trait IntoIntIterator { // desugared
    type IntoIntIter: Iterator<Item = u32>;
    fn into_int_iter(self) -> Self::IntoIntIter;
}
}

In both cases, it would sometimes be nice to be able to name that return type! Of course, people can introduce type aliases or associated types (similar to the desugared form), but that is inconvenient, and it requires that the API author has made sure to do so.

Turbofish and impl Trait in argument position

impl Trait in argument position introduces a new generic parameter to the function:


#![allow(unused)]
fn main() {
fn collect_to_vec<T>(iter: impl Iterator<Item = T>) -> Vec<T> {
    iter.collect()
}

// is roughly equivalent to:

fn collect_to_vec_desugared<T, I>(iter: I) -> Vec<T>
where
    I: Iterator<Item = T>,
{
    iter.collect()
}
}

With the desugared version, users can write an expression that manually specifies the values for its type parameters:


#![allow(unused)]
fn main() {
let f = collect_to_vec_desugared::<u32, vec::IntoIter<u32>>;
}

The question addressed here is whether it should be possible to write an equivalent expression for collect_to_vec.

Background material

This RFC covers some esoteric corners of Rust's type system. This section provides background material that introduces terms that are necessary to understand what follows.

Function definition types

Currently, when you define a function my_function, this declares a value my_function that can be accessed by the user. The type of that value, however, is a bit complicated. Consider this program:

fn my_function() {
    println!("Hello, world");
}

fn main() {
    let f = my_function;
    println!("{}", std::mem::size_of_val(&f));

    let g: fn() = my_function;
    println!("{}", std::mem::size_of_val(&f));
}

Here we reference my_function twice, once to create a variable named f with an inferred type, and once to declare a variable g with type fn(). You might expect that the type of f would be inferred to fn() and that these two variables are equivalent, but if you run this program you will find that their sizes are different: 0 and 8 respectively (playground). What is going on?

The answer is that the type of my_function is actually a special zero-sized type called a function definition type (hereafter: fndef). There is a unique fndef type for every function in your program. These types implement the Fn traits, so they can be called: when you call one, the compiler knows exactly what function was called just from the type, since it is tied to a specific function. In contrast, the fn() type represents a function pointer -- it too can be called, but, without some kind of dataflow analysis, the compiler doesn't know what function is being called. This is analogous to using traits with generic types vs dyn Trait.

There is currently no syntax for a fndef type, so there is no way to write the type of f above. The closest you can do is an as-yet-unimplemented extension impl Trait, impl trait in let bindings, which would let you write let f: impl Fn() = my_function.

Generic function definition types

The fndef type for a generic function is itself generic. You can specify the values for those generics using turbofish:

fn generic_fn<T>(t: T) { }

fn main() {
    let f = generic_fn::<u32>;
}

Early vs late binding

Rust groups generic parameters on functions into two categories: early vs late bound. Early bound parameters are those whose values must be supplied when the function is referenced (either explicitly via turbofish, or implicitly with inference). Late-bound parameters are parameters that are bound only at the time when the function is called. At present, generic type and const parameters are always early bound; lifetime parameters can be either early or late depending on whether they are referenced outside of the argument listing (the exact rules are not pertinent to this RFC).

It is perhaps easiest to understand this by considering the impl of the FnOnce trait for a fndef type. Consider this example, of a function create_with_default that will create an instance of C. The input supplied to the creation function is either the string input or -- in some cases -- a default string.


#![allow(unused)]
fn main() {
trait Create {
    fn create(input: &str);
}

fn create_with_default<'a, C: Create>(input: &'a str) -> C {
    if input.is_empty() {
        C::create("default")
    } else {
        C::create(input)
    }
}
}

We will refer to the fndef type of create_with_default as create_with_default1. Conceptually, there is an impl of FnOnce for create_with_default rather like so:


#![allow(unused)]
fn main() {
impl<'a, C> FnOnce<(&'a str,)> for create_with_default<C> {
    //             ^^^^^^^^^^                         ^^^
    //                 |                               |
    //                 |            Early bound type parameters
    //                 |            appear in this list.
    //                 |
    // Late bound parameters appear only in these
    // argument types.
    type Output = C;

    fn call(self, args: (&'a str,)) {
        (self)(args.0)
    }
}
}

Here there are two "input types" supplied to the trait: the argument parameter A to FnOnce<A>, which defines the types of the arguments being supplied, and the self type (create_with_default<C>), which defines the type of the value being called. The set of generic parameters on the impl is the same as the set on the fn declaration. We can classify those parameters in two ways:

  • Late-bound parameters are those that appear only in the arguments -- this means that they different values can be supplied for them each time the function is called. In this example, the lifetime 'a is late-bound.
  • Early-bound parameters are those that appear in the Self type. This means that they are "baked into" the function that is being called (which always has a single type) and cannot change from call to call. In this example, C is early-bound.

In general, it is better for parameters to be late-bound, as that allows the user more flexibility. However, because of the rules that every impl must be constrained2, parameters that don't appear in the argument types must appear in the Self type, and hence be early bound. In practice, the compiler today only allows lifetimes to be late-bound; all type parameters are early bound. This desugaring however shows that it is possible to consider type parameters as late-bound as well.

3

...which are there to ensure that the compiler can always figure out what impl to invoke at monomorphization time.

1

Surprise! This is precisely the notation that this RFC is going to propose!

Guide-level explanation

Please refer to the impl trait explainer, which has a section on names for return position impl trait that aims to be a "end-user manual" for this feature.

Reference-level explanation

The proposal has a few parts:

  • Named fndef types:
    • For every function my_function, define both a value and a type, except when there is already a type with that name.
    • As a disambiguator, also export a type fn#my_function that can be used to unambiguously name the fndef type.
    • In traits, for every fn my_function item, introduce a type my_function associated type (except when there is already an associated type with that name).
  • Late-bound argument position impl Trait:
    • Change impl Trait in argument position so that it desugars to "late-bound types"
    • In so doing, settle the question of whether impl Trait in argument position can be constrained with turbofish (no)
  • Random bug fixes:
    • Inherent associated types (impl Foo { type Bar = u32; } and you can write Foo::Bar).
    • Fix the Foo::Output code in the compiler (not really RFC worthy).

Named fndef types

Introduce a set of changes designed to make it so that users can name the fndef type for every function by writing types whose name is the same as the name that refers to the fn by value:

  • foo to name the type for a top-level fn foo.
  • Type::foo to name the type for an inherent method foo.
  • <T as Trait>::foo to name the type for a trait method foo (or Trait::foo and so forth).

Top-level function items

Top-level function declarations like fn my_function will also export a value in the type namespace with the same name (my_function) that corresponds to the fndef type for that function. Therefore, the following works:

fn foo() {}

struct SomeType {
    f: foo // refers to the fndef type for `foo`
}

fn main() {
    let x = Sometype { f: foo };
}

Function definitions in traits

Function declarations in inherent and trait impls introduce an associated type into the surrounding impl with the same name as the function whose value is the fndef type for the function:


#![allow(unused)]
fn main() {
trait Clone {
    // Introduces the equivalent of:
    //
    //     type clone;
    fn clone(&self);
}
}

Function definitions in impls

Function declarations in inherent and trait impls introduce an associated type into the surrounding impl with the same name as the function whose value is the fndef type for the function:


#![allow(unused)]
fn main() {
struct MyType { }

impl MyType {
    // Introduces:
    //
    //     type new = <fndef for new>;
    fn new() -> Self {

    }
}

impl Clone for MyType {
    // Introduces:
    //
    //     type clone = <fndef for clone>;
    fn clone(&self) -> ... {

    }
}
}

Shadowing rules

Although Rust's naming conventions make this unlikely, it is possible today to define a type and a function with the same name. This obviously presents a conflict in attempting to introduce types with the same names as functions. This section discusses the possible shadowing that can arise in each case and how we manage it. The general strategy is to only generate the fn type if there is not already a type defined with the same name.

Edition Rust.next

The plan is that in the edition Rust.next (presumably Rust 2024), all cases of shadowing will become hard errors. In the current edition, however, cases of shadowing yield warnings where needed to maintain backwards compatibility.

Traits and trait impls

If a trait defines an explicit associated type with the same name as one of its functions, then the associated type for that function is suppressed. This also applies to associated types defined in supertraits. Therefore:


#![allow(unused)]
fn main() {
trait Foo: Bar {

    // A `foo` associated type exists: 
    //
    // nothing is generated for the `fn foo`.
    type foo;
    fn foo(&self);

    // A `bar` associated type exists in the supertrait:
    //
    // nothing is generated for the `fn bar`.
    fn bar(&self);

    // The `Bar` supertrait defines an associated type `baz`, but implicitly:
    //
    // generate an associated type `baz.`
    fn baz(&self);
}

trait Bar {
    type bar;

    fn baz(&self);
}
}

In all of these cases, we issue warnings. In Rust 2024, this will be an error.

Rationale: Currently given T: Foo, T::bar would be legal today, so we don't want to generate a type there. But otherwise we do.

Inherent impls

Inherent impls do not support associated types today, so we don't have to worry about conflicts.

If an associated type defined on T has the same name as an inherent function on T, then we report an error.

Tuple struct and enum variants

Although not a function declaration, tuple structs and enum variants define constructor functions with the same name as the type itself:


#![allow(unused)]
fn main() {
struct Foo(u32); // defines a fn `Foo` of type `u32 -> Foo`
}

In this case, we simply do not generate a fndef type for the constructor. (This is also true in Rust 2024.)

Top-level functions

Top-level functions can conflict with named types in a number of ways (examples follow). In all such cases, the intent is to have the function not generate a type alias.

Conflict with prelude (playground)

fn Vec() {
    
}

fn main() {
    let x = Vec();
}

Conflict with name from use statement (playground)

mod foo {
    pub type bar = u32;
}

use foo::bar;

fn bar() { }

fn main() {
    
}

Conflict with macro-generated items (playground)

mod foo {
    macro_rules! make_bar {
        () => { pub type bar = u32; }
    }
    
    make_bar!();
}

use foo::bar;

fn bar() { }

fn main() {
    let x: bar = 22_u32;
}

Late-bound argument position impl Trait and turbofish

Currently, impl Trait in argument position is desugared to the equivalent of another explicit parameter:


#![allow(unused)]
fn main() {
fn foo(x: impl Debug) { }

// becomes roughly equivalent to
// fn foo<D: Debug>(x: D) { }
}

This parameter is currently considered "early-bound". As discussed in the motivation, that means that the fndef type foo<D> is generic over some D: Debug. Conceptually it looks something like this:


#![allow(unused)]
fn main() {
struct foo<D: Debug>;
impl<D: Debug> FnOnce<(D,)> for foo<D> {
    ...
}
}

This means that if you were to try and name the type of foo, say in a struct, you would need some to way to specify the value of D:


#![allow(unused)]
fn main() {
struct Wrapper {
    f: foo // What is the value of D here?
}
}

We could allow you to write foo<u32>, but that would be strange, because if you look at the definition of foo, there are no declared type parameters. What's more, if there were multiple impl Trait, we'd have to define an arbitrary ordering for them. Overall, we'd prefer for people to be able to think of impl Trait more intuitively without having to understand the desugaring.

As noted in that section, however, generic parameters that appear in the argument types can be made late-bound instead of early-bound. In the case of impl Trait arguments, they always (by definition) appear in the argument types. Therefore, we could make them "late-bound", so that they are only parameters of the impl and not of the type. Conceptually then the fndef type for foo would be like this:


#![allow(unused)]
fn main() {
struct foo;
impl<D: Debug> FnOnce<(D,)> for foo {
    ...
}
}

As a result, the type of foo is just written as foo, with no type arguments, and hence this struct is perfectly legal:


#![allow(unused)]
fn main() {
struct Wrapper {
    f: foo
}
}

What's more, making impl Trait late bound is actually more flexible. For example, this code does not compile today, but it would under this proposal:

fn foo(d: impl Debug) { /* ... */ }

fn main() {
    let f = foo;
    f(22_u32); // call once with `u32`
    f(22_i32); // call again with `i32`
}

Turbofish interaction

This also settles 'en passante' on of the open questions about impl Trait in argument position: should you be allowed to specify their value in turbofish? Clearly, the answer under this proposal is no, as there are no type parameters whose value needs to be specified.

Implication: migration to impl Trait is not fully backwards compatible

Settling the turbofish question in this matter does mean that one cannot migrate from a generic function to impl Trait with perfect fidelity:


#![allow(unused)]
fn main() {
fn foo<D: Debug>(d: D) { }
}

is different than


#![allow(unused)]
fn main() {
fn foo(d: impl Debug) { }
}

because the former permits turbofish and the latter does not.

Implication: Backwards incompatibility around inference

Unfortunately, there is a slight (largely theoretical) backwards incompatibility with making impl Trait type parameters late bound. It is possible today to leverage inference across calls to the same function. The following code compiles today but would become an error in the future:

fn foo(d: impl Debug) { /* ... */ }

fn main() {
    let f = foo;
    f(None); // call with `Option<_>`, unknown value type
    f(Some(22_i32)); // call again with `Option<i32>`
}

Today, we are able to infer that both calls must be using an Option<i32>. This works because all calls to f must use the same value for the impl trait (it is early bound). If it becomes late bound, that is no longer true, so we would require the f(None) call to use an explicit type annotation (e.g., f(None::<i32>)). This form of breakage is permitted by our semver rules. We judge the likelihood of this impacting many crates to be small, but we will have to test it.

Implication: Backwards incompatibility around inference

Drawbacks

Giving values for uncaptured parameters

Impl trait in return position do not capture all

Rationale and alternatives

Why not introduce the typeof keyword?

It has been proposed to use the typeof keyword to permit users to take the resulting type from arbitrary expressions. This would mean that one could


#![allow(unused)]
fn main() {
fn foo<T> { }
}

Why not introduce a named type

Why not introduce a named type into the environment?

It is difficult to decide

Prior art

Discuss prior art, both the good and the bad, in relation to this proposal. A few examples of what this can include are:

  • For language, library, cargo, tools, and compiler proposals: Does this feature exist in other programming languages and what experience have their community had?
  • For community proposals: Is this done by some other community and what were their experiences with it?
  • For other teams: What lessons can we learn from what other communities have done here?
  • Papers: Are there any published papers or great posts that discuss this? If you have some relevant papers to refer to, this can serve as a more detailed theoretical background.

This section is intended to encourage you as an author to think about the lessons from other languages, provide readers of your RFC with a fuller picture. If there is no prior art, that is fine - your ideas are interesting to us whether they are brand new or if it is an adaptation from other languages.

Note that while precedent set by other languages is some motivation, it does not on its own motivate an RFC. Please also take into consideration that rust sometimes intentionally diverges from common language features.

Unresolved questions

  • What parts of the design do you expect to resolve through the RFC process before this gets merged?
  • What parts of the design do you expect to resolve through the implementation of this feature before stabilization?
  • What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC?

Future possibilities

Think about what the natural extension and evolution of your proposal would be and how it would affect the language and project as a whole in a holistic way. Try to use this section as a tool to more fully consider all possible interactions with the project and language in your proposal. Also consider how this all fits into the roadmap for the project and of the relevant sub-team.

This is also a good place to "dump ideas", if they are out of scope for the RFC you are writing but otherwise related.

If you have tried and cannot think of any future possibilities, you may simply state that you cannot think of anything.

Note that having something written down in the future-possibilities section is not a reason to accept the current or a future RFC; such notes should be in the section on motivation or rationale in this or subsequent RFCs. The section merely provides additional information.

😕 Frequently asked questions

This page lists frequently asked questions about the design. It often redirects to the other pages on the site.

What is the goal of this initiative?

See the Charter.

Who is working on it!

See the Charter.