Search code examples
rustreturnserde-json

What's the difference between using the return statement and omitting the semicolon in Rust?


I'm writing a function that returns a serde_json::Value upon success (and failure). Previously in Rust I have been omitting the semicolon to return data from a function, like in the code example below:

use serde_json::{Result, Value};
use core::result::Result as ResultCore;

fn returning_function() -> ResultCore<Value, Value> {
    let data = r#"
    {
        "status": "ok",
        "response": {
            "data": "secret message!"
         }
    }
    "#;

    match str_to_json(data) {
        Ok(json_data) => match json_data["status"].as_str() {
            Some(status_str) => {
                if status_str == "ok" {
                    Ok(json_data["response"].clone())
                }
            }
            None => eprintln!("\"status\" was not a string")
        }
        Err(error) => eprintln!("something went wrong! here's what: {}", error)
    }

    Err(serde_json::Value::Null)
}

fn str_to_json(json_data: &str) -> Result<Value> {
    Ok(serde_json::from_str(json_data)?)
}

Here comes the part I don't understand: this doesn't compile. Rust's compiler tells me "mismatched types", and that it expected type (), but found type serde_json::value::Value. Now, I found a solution to this that does compile, and it is as follows:

use serde_json::{Result, Value};
use core::result::Result as ResultCore;

fn returning_function() -> ResultCore<Value, Value> {
    let data = r#"
    {
        "status": "ok",
        "response": {
            "data": "secret message!"
         }
    }
    "#;

    match str_to_json(data) {
        Ok(json_data) => match json_data["status"].as_str() {
            Some(status_str) => {
                if status_str == "ok" {
                    return Ok(json_data["response"].clone());
                    // ^ added return statement here
                }
            }
            None => eprintln!("\"status\" was not a string")
        }
        Err(error) => eprintln!("something went wrong! here's what: {}", error)
    }

    Err(serde_json::Value::Null)
}

fn str_to_json(json_data: &str) -> Result<Value> {
    Ok(serde_json::from_str(json_data)?)
}

By adding the return statement the compiler suddenly is happy and the compiler doesn't have anything to say about it any more. Why is this? I was under the impression that omitting the semicolon and using the return statement had the same implications ­— why does it differ here?


Solution

  • A return statement, otherwise known as an early return, will return an object from the last/innermost function-like scope. (Function-like because it applies to both closures and functions)

    let x = || {
        return 0;
        println!("This will never happen!");
    };
    fn foo() {
        return 0;
        println!("This won't happen either");
    }
    

    An absent semicolon will instead evaluate the expression, like a return, but only return to the last/innermost scope, or in other words, it returns from within any set of {}.

    let x = {           // Scope start
        0
    };                  // Scope end
    
    fn foo() -> usize { // Scope start
        0
    }                   // Scope end
    

    return statement will break out of any amount of nested scopes until it hits a function-like scope:

    fn foo() -> usize {// <------------------------------------------\
        {                                                      //    |
            {                                                  //    |
                {                                              //    |
                    {                                          //    |
                        {                                      //    |
                            {                                  //    |
                                {                              //    |
                                    {                          //    |
                                        {                      //    |
                                            {                  //    |
                                                {              //    |
                                                     return 0; // ---/
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    

    The return statement also has a type of its own, that is to say that let x = return; will actually compile.

    A return statement will evaluate to !, AKA the never type. You can't name it in stable rust right now, but it will eventually become stable and usable.