Rfcs: Deprecate "implicit ()" by making it a compilation error.

Created on 6 Aug 2017  路  6Comments  路  Source: rust-lang/rfcs

Premise: The "implicit ()" behavior of a trailing semi-colon should never be used for well-written, auditable code except when a brace-expression is used directly in expression statements. In addition to narrowing valid syntax to remove a case no readable code should use, it would remove a frequent source of errors for new rust programmers.

Proposal: The rule should change to: "If a {鈥 expression does not terminate with an explicit expression, then if it is directly an expression statement (ie: {鈥;) then implicitly append a () result expression, else fail with a compilation error which requires an explicit result expression.

Example A:

fn main() {
    let v = {
        println!("hacky debug: v is being initialized.");
        42;
    };
    assert!(v == 42);
}

With my somewhat recent compiler, I get this error:

$ rustc --version
rustc 1.17.0 (56124baa9 2017-04-24)

$ cargo build
   Compiling rustexperiments v0.1.0 (file:///home/user/rustexperiments)
error[???]: missing expression
 --> src/main.rs:6:18
  |
6 |     assert!(v == 42);
  |                  ^^ expected (), found integral variable
  |
  = note: expected type `()`
             found type `{integer}`

error: aborting due to previous error

error: Could not compile `rustexperiments`.

To learn more, run the command again with --verbose.

If this proposal were accepted, the error would occur earlier and the message might look like this:

$ rustc --version
rustc v<Nathan's fantasy rust>

$ cargo build
   Compiling rustexperiments v0.1.0 (file:///home/user/rustexperiments)
error[E0308]: mismatched types
 --> src/main.rs:5:5
  |
5 |     };
  |      ^^ expected expression.
  |
  = note: Possible fixes: remove the trailing semicolon or add an explicit result expression. Note, older versions of rust implicitly added the result expression `()`.

error: aborting due to previous error

error: Could not compile `rustexperiments`.

To learn more, run the command again with --verbose.

Example B:

fn main() {
    let v = 42;

    {
        let x = gather_system_info();
        let y = gather_user_info();
        println!("System info: {}; User info: {}\n", x, y);
    };

    assert!(v == 42);
}

In both current rust (as accepted by rustc v1.17.0) and a future rust which accepted this proposal, this code will compile identically without errors.

T-lang

Most helpful comment

clippy can't do anything right now if a compiler error occurs before our lints run and most our lints are run after type checking.

I agree though that example A should backtrack the source of the value and see if there's the possibility of removing a semicolon in the presence of ().

Note that there are many other situations, e.g. the reverse of the above comparison:

fn main() {
    let v = {
        println!("hacky debug: v is being initialized.");
        42;
    };
    assert!(42 == v);
}

produces

   Compiling playground v0.0.1 (file:///playground)
error[E0277]: the trait bound `{integer}: std::cmp::PartialEq<()>` is not satisfied
 --> src/main.rs:6:16
  |
6 |     assert!(42 == v);
  |                ^^ can't compare `{integer}` with `()`
  |
  = help: the trait `std::cmp::PartialEq<()>` is not implemented for `{integer}`

I do not think that this requires an RFC. Simply implementing improved diagnostics and opening a PR is totally fine (I've not seen a denied diagnostic improvement PR so far).

All 6 comments

Oh, also, I recognize that the language design has had a _lot_ of input from many people, and that there are always trade-offs. I wouldn't be at all surprised if this exact proposal has already been made, or at least many similar proposals, so I hope the language experts can forgive any duplication or re-opening of worms.

If this is the case, a simple statement of "we already considered this and decided not to" would satisfy me. Even better though would be a link to a discussion or similar ticket.

I can confirm the same result with the following versions:

root@REDACTED:/Users/zancas/rust/invalidunit# cargo run
   Compiling invalidunit v0.1.0 (file:///Users/zancas/rust/invalidunit)
error[E0308]: mismatched types
 --> src/main.rs:6:26
  |
6 |             assert!(v == 42);
  |                          ^^ expected (), found integral variable
  |
  = note: expected type `()`
             found type `{integer}`

error: aborting due to previous error

error: Could not compile `invalidunit`.

To learn more, run the command again with --verbose.
root@REDACTED:/Users/zancas/rust/invalidunit# rustc --version
rustc 1.18.0 (03fc9d622 2017-06-06)
root@REDACTED:/Users/zancas/rust/invalidunit# cargo --version
cargo 0.19.0 (28d1d60d4 2017-05-16)
root@REDACTED:/Users/zancas/rust/invalidunit#

Sounds to me like you actually want a _lint_ for that, or perhaps just improved diagnostic for the cases where it causes an error. It's not customary in Rust to treat bad style with hard errors if it's legal code. Have you checked whether clippy has this already? If not, you could get it added there (which in turn would increase the likelihood it would eventually get added into the compiler).

The first error you got was caused by this:

42;

You weren't returning that value, but you said the fix would be for the lint to say

};

This was the issue. I agree the compiler error there isn't exactly straightforward but it is telling you what the issue is (sort of). I think that can be improved but not the way in which you've laid out. This stumbling block seems best suited for clippy or improving the current error but not changing the way the grammar of the code currently works.

clippy can't do anything right now if a compiler error occurs before our lints run and most our lints are run after type checking.

I agree though that example A should backtrack the source of the value and see if there's the possibility of removing a semicolon in the presence of ().

Note that there are many other situations, e.g. the reverse of the above comparison:

fn main() {
    let v = {
        println!("hacky debug: v is being initialized.");
        42;
    };
    assert!(42 == v);
}

produces

   Compiling playground v0.0.1 (file:///playground)
error[E0277]: the trait bound `{integer}: std::cmp::PartialEq<()>` is not satisfied
 --> src/main.rs:6:16
  |
6 |     assert!(42 == v);
  |                ^^ can't compare `{integer}` with `()`
  |
  = help: the trait `std::cmp::PartialEq<()>` is not implemented for `{integer}`

I do not think that this requires an RFC. Simply implementing improved diagnostics and opening a PR is totally fine (I've not seen a denied diagnostic improvement PR so far).

Refiled as a diagnostics issue at https://github.com/rust-lang/rust/issues/55076.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

3442853561 picture 3442853561  路  3Comments

steveklabnik picture steveklabnik  路  4Comments

yongqli picture yongqli  路  3Comments

torkleyy picture torkleyy  路  3Comments

mahkoh picture mahkoh  路  3Comments