Why cannot I mix return and expression in methods

Viewed 78

Why do the following two pieces of code compile:

A)

impl Rectangle {
    fn can_hold(&self, other : &Rectangle) -> bool{
        if self.width > other.width && self.height > other.height {
            return true;
        }
        return false;
    }
}

B)

impl Rectangle {
    fn can_hold(&self, other : &Rectangle) -> bool{
        if self.width > other.width && self.height > other.height {
            true
        }else{
            false
        }
    }
}

But not this:

C)

impl Rectangle {
    fn can_hold(&self, other : &Rectangle) -> bool{
        if self.width > other.width && self.height > other.height {
            true
        }
        return false;
    }
}

which fails to compile with the error message

expected `()`, found `bool`

Why does the compiler think there is a () return type?

Actually also I wonder why you cannot do something like this:

D)

impl Rectangle {
    fn can_hold(&self, other : &Rectangle) -> bool{
        if self.width > other.width && self.height > other.height {
            true
        }
        false
    }
}

Because I think this could be perfectly parsed by a compiler and the intention is clear (or not?). Why wouldn't it be clear I would want to return the result of this expression.

2 Answers

(A) works because whether or not the if-block is entered, execution will encounter a return statement which terminates the method and returns a value of type bool as expected by the method signature.

(B) works because only one of the if/else blocks can be entered, and both of them contain an expression which evaluates to type bool. Because there is no more code which can be reached after the if/else, Rust will take the encountered expression to be taken as the return value for the method, type bool as expected by the method signature.

In (C) you have the expression true within the if block. But a method will only return the value of an expression if it is the last line of code which is reached within the method. And in (C) you have true, then the if-block ends, and then you call return false. So Rust sees that the line which contains true is not the final line that will be reached (because there's nothing to stop execution continuing after the if-block) and so Rust does not expect a return type on this line (so it expects the () type which indicates no return type). If you were to use return true inside the if-block (bringing you back to the (A) pattern), then Rust would know that execution of the method stops here and the specified value would be returned.

(D) will fail for the same reason as (C) (because the true expression will not be the last line that executes even if the if-block is entered).

Note that you don't need if or else in this example, because the simplest way to write this entire method is like this:

impl Rectangle {
    fn can_hold(&self, other : &Rectangle) -> bool{
        self.width > other.width && self.height > other.height
    }
}

Now the only line within the method is an expression which evaluates to true or false, and this value is what will be returned by this method.

In (C), you can also think of the if expression returning a value which isn't the function's return value unless that was the last expression that was executed. You could for instance, use that in a let assignment.

impl Rectangle {
    fn can_hold(&self, other : &Rectangle) -> bool{
        let k = if self.width > other.width && self.height > other.height {
            true
        } else {
            false
        };
        return k;
    }
}

Obviously this is redundant, as @Bobulous explains, but it helps illustrate where the returned value is actually going.

Related