I like the idea of this. My opinions:
-
() is a special type that in many cases its use can be optimized out or implied. So it is fairly reasonable to make special rules to only this type, and I believe the rule should apply to all () returning expressions, not just assignment expressions.
- Rather than restricting some special uses of this type, we may want to think about the use cases of this type and define special rules that apply to this type only, so it wouldn’t affect the beauty of the type system.
- For example, I suggest that any expression that returns
() should not be used as function parameter or assignments (including struct constructions), except within an explicit block like (a=b) or {a=b}. This also apply to f(drop(a)) where you have to write f((drop(a))).
- The error message (or lint message) may read “An expression of type
() must be placed in braces”.
Examples
fn identical<T>(t:T) -> T {
t
}
fn do_nothing() {
}
fn main(){
let (mut a,b)=(1,1);
//Illegal: `a=b` returns ()
//identical(a=b)
identical((a=b));
//Illegal: match statement returns ()
//identical(match () { () => do_nothing() })
identical(match () { () => 1 }); //Ok, match statement does not return ()
//Illegal
//After type inference, identical(()) returns ()
//let v = (identical(()),2)
}
Changes on teaching
Before
() is the type, () is the value, { a=b; } is shorthand for { a=b; () }
After
() is the type, <empty expression> is the value. To specify that we have a value that can be represented by an empty expression, we need to put it inside () otherwise it will lead to expressions like f(,1) or let a = ;, which is wired and we don’t want to allow. { a=b; } is legal because it contains a statement and an empty expression. (and empty expression is allowed as the returning piece of a block). Another piece that empty expression is allowed is in a return statement.