Start learning free

LESSON 43 OF 414 · FUNCTIONS I

Quiz: A Rust Function with a Forgotten Return

Quiz Read the full explanation

Quiz: What would this code produce if we were to copy-paste it in a code editor and run it?

fn get_double(n: i32) -> i32 {
	n * 2;
}

fn main_function() {
	let result = get_double(10);
	println!("{}", result);
}

Important Note:

Do not use an actual code editor to get the answer! It would defy the whole purpose of the quiz!

Pick your answer

Next lesson

RUST · FUNCTIONS I

Quiz Forgotten Return in Rust, explained

The question

The quiz shows a function that is supposed to double a number, and a caller that prints the result:

fn get_double(n: i32) -> i32 {
    n * 2;
}

fn main_function() {
    let result = get_double(10);
    println!("{}", result);
}

The options are: A, it prints 20; B, it produces errors; C, it prints 10. The answer is submitted by assigning the chosen letter to a variable named answer.

How to think about it

Start with the signature. -> i32 is a promise that get_double hands back an i32. Then check how the body keeps that promise.

The body is the single line n * 2;. The multiplication is computed, but the semicolon makes the line a statement, and a statement's value is discarded. A Rust function returns its final expression, the last line written without a semicolon, or whatever a return sends back. This body has neither. With nothing to return, the body evaluates to (), the empty unit value.

So the function promises an i32 and delivers (). Rust checks this when it compiles, before anything runs. The compiler reports:

error[E0308]: mismatched types
  |
1 | fn get_double(n: i32) -> i32 {
  |    ----------            ^^^ expected `i32`, found `()`
  |    |
  |    implicitly returns `()` as its body has no tail or `return` expression
2 |     n * 2;
  |          - help: remove this semicolon to return this value

The program does not build, so no number is printed at all. That rules out A and C. Option C, 10, would only make sense if the function returned its argument unchanged, which nothing in the code does. The answer is B.

The solution in Rust

The lesson asks for the letter as an owned String, created by calling to_string on the literal:

let answer: String = "B".to_string();

/* Explanation:
Notice that inside the function we only compute n * 2 but do nothing with the result afterwards, we do not return it from the function.

A function with no return statement inside it is called a "void" function and should be defined accordingly, like this:
fn get_double(n: i32) {
    n * 2;
}

But because we defined our function as an "i32" function ( fn get_double(n: i32) -> i32 ), Rust expects an i32 to be returned from the function.
And so when Rust notices that there is no actual "return" statement inside the function, it throws an error.
*/

The type annotation : String is optional, since the compiler can infer it, but it makes the intent clear. A string literal on its own would be a &str, so to_string creates the owned String.

How to fix the function

There are two equivalent fixes. Remove the semicolon so n * 2 is the final expression, or write return n * 2;. With either one, get_double(10) returns 20. The comment above mentions the alternative of removing -> i32. The function then compiles, but it returns (), so result holds the unit value. The caller's println!("{}", result) then fails with `()` doesn't implement `std::fmt::Display`, because a plain {} cannot print (). Removing the type moves the error instead of fixing it.

After the fix, the function can be used anywhere an i32 is expected. For example, get_double(get_double(3)) evaluates to 12, because the inner call returns 6 and the outer call doubles it. That only works because the value is really returned.

A useful habit for quizzes like this is to ask two questions in order: does it compile, and only then what does it print. Only when the answer to the first is yes does the second matter.

Common mistakes

  • Thinking the last computed value is returned automatically. That is how some languages behave, but in Rust the semicolon decides.
  • Choosing 20. It is the answer the author intended. The code as written does not compile.
  • Assuming a warning is enough. Some problems in Rust are only warnings, such as an unused variable. A mismatched return type is a hard error.

Key takeaways

  • The return type in the signature must match what the body produces.
  • A trailing semicolon turns a value into a statement, leaving ().
  • Rust reports the mismatch at compile time and the compiler points at the semicolon.
Keep going with RustAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
Start learning free