在 Rust 中,使用 return 语句和省略分号有什么区别?

9
我正在编写一个函数,当成功(或失败)时返回serde_json::Value。在 Rust 中,我以前省略了分号来从函数中返回数据,就像下面的代码示例中一样:
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)?)
}

接下来是我不理解的部分:这段代码无法编译。Rust编译器告诉我“类型不匹配”,它期望的类型是(),但实际上找到的类型是serde_json::value::Value。现在,我找到了一个可编译的解决方案,如下所示:

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)?)
}

通过添加return语句,编译器突然变得满意了,而且编译器再也没有什么可说的了。为什么会这样?我曾经以为省略分号和使用return语句具有相同的含义 - 为什么这里不同呢?

1
return 语句会从整个函数中返回,而一个代码块的最后一个表达式(如果省略了分号)隐式地只从该代码块中返回。请参阅 What type is the “type ()” in Rust? 中的答案以获取更多讨论。 - SCappella
3个回答

12
< p >一个return语句,也被称为早期返回,将从最后/最内部的函数式范围返回一个对象。 (函数式是因为它适用于闭包和函数)< /p >
let x = || {
    return 0;
    println!("This will never happen!");
};
fn foo() {
    return 0;
    println!("This won't happen either");
}

缺少分号会使表达式被计算,就像 return 一样,但只会返回到最后/最内层的作用域,或者换句话说,它从任何一组 {} 中返回。
let x = {           // Scope start
    0
};                  // Scope end

fn foo() -> usize { // Scope start
    0
}                   // Scope end

return语句会跳出任意数量的嵌套作用域,直到它遇到类似函数的作用域:

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

return语句也有自己的类型,也就是说let x = return;实际上是可以编译通过的。

return语句将计算为!,也就是never类型。目前在稳定版本的Rust中你不能给它命名,但它最终会变得稳定并可用。


这对我来说是一个陷阱。真的是一个很好的解释。 - Pipe Runner

5

《Rust编程之道》所述:

在Rust中,函数的返回值等同于函数体块中最后一个表达式的值。

换句话说,一个表达式没有分号并不意味着它是返回值,而是因为它是函数中的最后一个表达式。分号用于分隔表达式,因此:

fn foo() -> i32 {
    5;
}

等价于一个产生值为5的表达式,后面跟着一个不产生任何值的空表达式。因此,上述函数将无法编译。

return关键字非常方便,如果你想在到达最终表达式之前就从函数中返回。这就是你在示例中尝试做的事情。

还要注意,所有可能的返回值都必须与函数本身的返回值具有相同的类型。

然而,以上内容并不能完全解释你所遇到的编译器错误。你的内部匹配看起来像这样:

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")
}

match块的规则之一是每个分支都必须评估为相同的类型。但在上面的情况下,一个分支可能评估为std::result::Result<serde_json::value::Value, _>,而另一个分支没有评估为任何值(或更精确地说,评估为空值())。
插入return可以避免该错误,因为Some分支现在从函数中返回,而不是评估为std::result::Result<serde_json::value::Value, _>类型的值。

0

上述错误实际上是由if表达式没有else而导致的隐式返回()。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接