RFC 1685: deprecate-anonymous-parameters

lang (syntax)

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:

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

Alternatives

Unresolved questions