Language design: implicit returns and type ()


#1

Implicit returns are sometimes confusing and maybe harmful. How about enforcing explicit returns or disallowing “;” after the last expression?

Dining philosophers example from the rust book is a good illustration of the problem. The code linked above was modified and does not compile because of the following error:

<anon>:39:11: 39:17 error: no method named `join` found for type `()` in the current scope
<anon>:39         h.join().unwrap();
                    ^~~~~~
note: in expansion of for loop expansion
<anon>:38:5: 40:6 note: expansion site
error: aborting due to previous error

But the actual error is on line 35:

let handles: Vec<_> = philosophers.into_iter().map(|p| {
    thread::spawn(move || {
        p.eat();
    }); // <-- The extra ";" is the error
}).collect();

A very simple and subtle typo resulted in obscure error message.

A compiler check that disallows the usage of type placeholder _ when the return type is known to be a union type () could do the trick here. So the example would fail to compile with a different error such as

let handles: Vec<_> = philosophers.into_iter().map(|p| {
error: ~~~~~~~~ ^
Type placeholder _ cannot be used for type (). Consider switching to Vec<()> or check assigned type.

That would either force programmer to explicitly declare type as () or protect from this particular type of issues.

However this doesn’t protect from cases like:

let handles: Vec<_> = philosophers.into_iter().map(|p| {
  Philosopher::new(&(p.name + " 1"));
}).map(|p| {
    thread::spawn(move || {
        p.eat();
    })
}).collect();

:36:15: 36:20 error: no method named eat found for type () in the current scope
:36 p.eat();
^~~~~

Maybe if “;” were disallowed after the last statement in the block that could help preventing these kinds of issues? or explicit returns? WDYT?


#2

I think all of your suggestions are breaking changes to the language (which means they’re unlikely to be accepted at this point).


#3

Sounds to me like better error messages would solve a great deal of this. You can also always add explicit return type annotations to your closures, but that can become unnecessarily verbose.

(I actually like it how it is. Using ; to convert expressions to statements is pretty cool.)


#4

That’s a bad idea; you no longer could end your block with a call to a function that returns something without returning its result.


#5

You could always do
{
x + 5;
()
}

if you really need to return nothing. And it would be more explicit.


#6

I agree that it’s nice, but my point is that it’s too easy to make a mistake on the last statement. The language is still young and it’s not too late to improve this.

Disallowing “;” after the last statement is not breaking too much. There could be a migration path. Existing codebase could easily be migrated with a simple tool (replace “; }” with “; () }”)


#7

People have gone apesh*t over us making changes to niche
behaviour that we had tested to not break anything. They’ve declared that we don’t
take semver or back-compat guarantees seriously.

This is a completely spurious change which would, in all likelihood, break every single Rust program in existence.

This will never happen.


#8

I don’t see how it could be harmful since you can’t really accidentally return something and then accidentally use it (although if anyone can think of a potential counter example, please share). So I think it’s consistent with the design philosophy of explicitness/correctness.

There are many situations where someone would not want to use one feature or another, so perhaps a separate checking tool that accepts custom rules and lists violations would be a preferable solution. This is done in safety critical applications, such as automotive, nuclear, aerospace, etc.


#9

I’d prefer the placeholder _ being assigned () be a warning. An error is a bit strong, but a warning could help fix this.


#10

By the way clippy recently gained a lint against unit-valued let bindings.