Pre RFC: deprecating anonymous parameters


#1

Hi! I’ve encountered the anon parameters issue a couple of times recently, so I decided to write up a pre-RFC. The main question is “Is it worth pursuing at all?” The issues is super minor, but annoys me personally a lot :slight_smile: All the typos, grammar and language changes can be suggested directly on GitHub.

  • Feature Name: deprecate_anonymous_parameters
  • Start Date: 2016-07-19
  • RFC PR: (leave this empty)
  • Rust Issue: (leave this empty)

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 do not pose any significant problems, but lead to a number of 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
    }
    
  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.

None of these issues is significant, but they exist.

Detailed design

Removing anonymous parameters from the language is formally a breaking change. The breakage can be trivially and potentially 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;
}

So the proposal is just to deprecate this syntax in the hope that the removal would become feasible and practical in the future. The hypothetical future may include:

  • Rust 2.0 release.
  • A 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.

Drawbacks

There are a number of drawbacks:

  • Deprecation will require code changes across the community without bringing any noticeable immediate benefits.

  • 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

  • How often are anonymous parameters used in practice?

  • Is there a consensus that anonymous parameters are not a useful language feature?


#2

RFC issue: https://github.com/rust-lang/rfcs/issues/1351


#3

I’m definitely in favor of fixing this in Rust 2.0, but practically this is such a minor issue that this doesn’t seem worth it in 1.x


#4

Yeah, that’s why I suggest only to deprecate this thing in 1.x. Deprecating “just” before 2.0 would be less effective because the time span will be shorter and because there perhaps would be a number of other deprecations at that time.


#5

Seems prudent, but only if we are serious about fixing the underlying problems someday. This will be a very long deprecation period, and cause a lot of pain, so if we never fix the problem (and it just remains deprecated forever) it will be fore nothing.

Why does the first surprising pattern happen? What’s special about 3 ampersands?


#6

It’s more like & and && are special cases. Most of other patterns, including &&&, are forbidden. If you allowed both arbitrary patterns and anonymous arguments, you’d need unbounded lookahead in the parser to discriminate between a type and a pattern.


#7

I’ve coded up an inspection (with a quick fix :slight_smile: ) for this. Running it on all the dependencies of Cargo, except the Cargo itself and standard library, gave three hits, two in rand:

and one in crossbeam:


#8

I use anonymous parameters all the time (I default to it, basically). I don’t know really have a reason - I guess I thought it was the standard thing to do. I kind of like them, but I wouldn’t mind making a change - especially because of the rather ghastly issue with pattern. It seems very bad that patterns don’t work for args in default methods.


#9

Man. I really thought we had decided to remove anonymous parameters a long time ago (like, before RFCs were around) – because of the parsing ambiguities – but obviously it never happened (or else I am remembering incorrectly). I agree they are nice but I think I also agree they ought to be deprecated (or even removed, if we can get away with it).


#10

Also, consider how this makes named parameters harder if we want them down the line.


#11

To throw a bit more data, Servo and all of its dependencies use anonymous parameters 34 times. Here is XML report if anyone is interested: https://gist.github.com/matklad/b40304aeaa176cd4db73716a5e315cd8


#12

RFC: https://github.com/rust-lang/rfcs/pull/1685