Summary

Currently Rust allows anonymous parameters in trait methods:

trait T {
    fn foo(i32);

    fn bar_with_default_impl(String, String) {

    }
}

This RFC proposes to deprecate this syntax. This RFC intentionally does not propose to remove this syntax.

Motivation

Anonymous parameters are a historic accident. They cause a number of technical annoyances.

  1. Surprising pattern syntax in traits

    trait T {
        fn foo(x: i32);        // Ok
        fn bar(&x: &i32);      // Ok
        fn baz(&&x: &&i32);    // Ok
        fn quux(&&&x: &&&i32); // Syntax error
    }
    

    That is, patterns more complex than _, foo, &foo, &&foo, mut foo are forbidden.

  2. Inconsistency between default implementations in traits and implementations in impl blocks

    trait T {
        fn foo((x, y): (usize, usize)) { // Syntax error
        }
    }
    
    impl T for S {
        fn foo((x, y): (usize, usize)) { // Ok
        }
    }
    
  3. Inconsistency between method declarations in traits and in extern blocks

    trait T {
        fn foo(i32);  // Ok
    }
    
    extern "C" {
        fn foo(i32); // Syntax error
    }
    
  4. Slightly more complicated syntax analysis for LL style parsers. The parser must guess if it currently parses a pattern or a type.

  5. Small complications for source code analyzers (e.g. IntelliJ Rust) and potential alternative implementations.

  6. Potential future parsing ambiguities with named and default parameters syntax.

None of these issues is significant, but they exist.

Even if we exclude these technical drawbacks, it can be argued that allowing to omit parameter names unnecessary complicates the language. It is unnecessary because it does not make Rust more expressive and does not provide noticeable ergonomic improvements. It is trivial to add parameter name, and only a small fraction of method declarations actually omits it.

Another drawback of this syntax is its impact on the learning curve. One needs to have a C background to understand that fn foo(T); means a function with single parameter of type T. If one comes from dynamically typed language like Python or JavaScript, this T looks more like a parameter name.

Anonymous parameters also cause inconsistencies between trait definitions and implementations. One way to write an implementation is to copy the method prototypes from the trait into the impl block. With anonymous parameters this leads to syntax errors.

Detailed design

Backward compatibility

Removing anonymous parameters from the language is formally a breaking change. The breakage can be trivially and automatically fixed by adding _: (suggested by @nagisa):

trait T {
    fn foo(_: i32);

    fn bar_with_default_impl(_: String, _: String) {

    }
}

However this is also a major breaking change from the practical point of view. Parameter names are rarely omitted, but it happens. For example, std::fmt::Display is currently defined as follows:

trait Display {
    fn fmt(&self, &mut Formatter) -> Result;
}

Of the 5560 packages from crates.io, 416 include at least one usage of an anonymous parameter (full report).

Benefits of deprecation

So the proposal is just to deprecate this syntax. Phasing the syntax out of usage will mostly solve the learning curve problems. The technical problems would not be solved until the actual removal becomes feasible and practical. This hypothetical future may include:

  • Rust 2.0 release.
  • A widely deployed tool to automatically fix deprecation warnings.
  • Storing crates on crates.io in “elaborated” syntax independent format.

Enabling deprecation early makes potential future removal easier in practice.

Deprecation strategy

There are two possible ways to deprecate this syntax:

Hard deprecation

One option is to produce a warning for anonymous parameters. This is backwards compatible, but in practice will force crate authors to actively change their code to avoid the warnings, causing code churn.

Soft deprecation

Another option is to clearly document this syntax as deprecated and add an allow-by-default lint, a clippy lint, and an IntelliJ Rust inspection, but do not produce compiler warnings by default. This will make the update process more gradual, but will delay the benefits of deprecation.

Automatic transition

Rustfmt and IntelliJ Rust can automatically change anonymous parameters to _. However it is better to manually add real names to make it obvious what name is expected on the impl side.

Drawbacks

  • Hard deprecation will cause code churn.

  • Soft deprecation might not be as efficient at removing the syntax from usage.

  • The technical issues can not be solved nicely until the deprecation is turned into a hard error.

  • It is not clear if it will ever be possible to remove this syntax entirely.

Alternatives

  • Status quo.

  • Decide on the precise removal plan prior to deprecation.

  • Try to solve the underlying annoyances in some other way. For example, unbounded look ahead can be used in the parser to allow both anonymous parameters and the full pattern syntax.

Unresolved questions

  • What deprecation strategy should be chosen?