Pre-RFC: Type parameter elision


#1

I’m posting this to figure out of the idea has been proposed before, and if it’s worth expanding to a full RFC. I didn’t find anything similar on this forum or in the RFCs repo, but I could have missed something. It’s a bit rough with only headings for some Alternatives and Drawbacks, mostly to save on effort at this point. :slight_smile:

  • Feature Name: type_parameter_elision
  • Start Date: (fill me in with today’s date, YYYY-MM-DD)
  • RFC PR: (leave this empty)
  • Rust Issue: (leave this empty)

Summary

Allow eliding type parameters with trait bounds in function signatures where there are no relationships between the parameters.

Motivation

Generic functions pay a big syntactic noise penalty compared to non-generic functions. Consider a function taking &str

fn foo(s: Vec<u8>)

We could make it more generally applicable by having it accept an Into<Vec<u8>> type, but that requires writing either

fn foo<S: Into<Vec<u8>>>(s: S)

or

fn foo<S>(s: S) where S: Into<Vec<u8>>

This RFC proposes allowing

fn foo(s: Into<Vec<u8>>)

This mirrors lifetime elision introduced in RFC 141.

In particular, this would make signatures for many functions accepting closures quite a bit nicer to read and write. For example, slice::binary_search_by could change from

binary_search_by<F>(&self, f: F) -> Result<usize, usize> where F: FnMut(&T) -> Ordering

to

binary_search_by(&self, f: FnMut(&T) -> Ordering) -> Result<usize, usize>

Detailed design

Bare trait bounds would be accepted in function signatures anywhere a type parameter could appear.

  • each such appearance introduces a type parameter bounded by the trait bound

  • introduced type parameters appear after all explicitly provided type parameters

  • bare trait bounds may only reference explicitly provided type parameters, or to type parameters in scope from outer impls, traits, or fns.

Examples

Here are a few examples from the standard library.

impl<T> Option<T> {
    pub fn unwrap_or_else(self, f: F: FnOnce() -> T) -> T { ... }            // elided
    pub fn unwrap_or_else<F: FnOnce() -> T>(self, f: F) -> T { ... }         // expanded

    pub fn map<U>(self, f: FnOnce(T) -> U) -> Option<U> { ... }              // elided
    pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Option<U> { ... }        // expanded
}

impl CString {
    pub fn new(t: T: Into<Vec<u8>>) -> Result<CString, NulError> { ... }     // elided
    pub fn new<T: Into<Vec<u8>>>(t: T) -> Result<CString, NulError> { ... }  // expanded
}

pub trait FromIterator<A> {
    fn from_iter(iterator: IntoIterator<Item=A>) -> Self;                    // elided
    fn from_iter<T>(iterator: T) -> Self where T: IntoIterator<Item=A>;      // expanded
}

impl <T> [T] {
    pub fn binary_search_by<T>(s: &[T], f: FnMut(&T) -> Ordering) -> Result<usize, usize> { ... }  // elided
    pub fn binary_search_by<T, F>(s: &[T], f: F) -> Result<usize, usize>                           // expanded
      where F: FnMut(&T) -> Ordering { ... }
}

impl str {
    pub fn trim_left_matches<'a>(&'a self, pat: Pattern<'a>) -> &'a str { ... }                    // elided
    pub fn trim_left_matches<'a, P>(&'a self, pat: P) -> &'a str where P: Pattern<'a> { ... }      // expanded
}

Drawbacks

Provides more than one way to write the same thing

Introduces additional subtleties

Alternatives

Status quo

Unresolved questions

Precision in rules for elision and expansion

Ability to elide type parameters for reference parameters

AsRef is a really useful place for this kind of elision, but it is used on references. As an example:

impl Path {
    pub fn new<S: AsRef<OsStr> + ?Sized>(s: &S) -> &Path { ... }
}

To elide the S parameter with the scheme in this RFC, we’d need to write

pub fn new<S: AsRef<OsStr> + ?Sized>(s: &AsRef<OsStr> + ?Sized) -> &Path { ... }

which is a trait object type.

Grammar and parser support

I don’t know if it’s possible or easy to disabiguate

fn foo(s: SomeStruct<String>)

from

fn foo(s: SomeTrait<String>)

Accepting traits in other positions

We could imagine allowing a trait bound to appear anywhere a type parameter can appear in signatures.

Interaction / compatibility with the impl Trait RFC


#2

You may be interested in RFC issue #302 (and the original filing with more discussion: #11196). I guess you may’ve already seen the optional extension to the original impl Trait RFC covering this too.

It doesn’t need to be distinguished at the grammar/parser level: they can result in the same internal AST structures, with the difference in behaviour guided by the compiler knowing what the thing actually is, i.e. if a name resolves to a trait it does the new thing, if it resolves to a struct it does the normal thing.


#3

Thanks for those links. I had not seen the original impl Trait RFC at all, and had missed the RFC issue as I was looking for the keyword “elision”.

Ah yeah that makes sense.