rust 使用 actix_web 时 main 函数为什么需要返回值?

rust 新手,只会 python 和 c

图片.png

use actix_web::{get, web, App, HttpServer, Responder};

#[get("/hello/{name}")]
async fn greet(name: web::Path<String>) -> impl Responder {
    format!("Hello {name}!")
}

#[actix_web::main] // or #[tokio::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .route("/hello", web::get().to(|| async { "Hello World!" }))
            .service(greet)
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

这里的 main 函数为什么需要一个返回值 std::io::Result<()> 呢?

按照写 c语言的经验,main 函数一般返回一个 int,表示程序退出状态码

而 rust 这里面,需要一个 std::io::Result<()> 就不能理解,这玩意都不是一个整数

删掉 main 函数的 std::io::Result<()> 还会报错

╰─➤  cargo run                                                                                                     130 ↵
   Compiling wmm v0.1.0 (/Users/ponponon/Desktop/code/me/wmm)
error[E0308]: mismatched types
 --> src/main.rs:8:1
  |
8 | #[actix_web::main] // or #[tokio::main]
  | -^^^^^^^^^^^^^^^^^
  | |
  | expected `()`, found enum `Result`
  | expected `()` because of default return type
  |
  = note: expected unit type `()`
                  found enum `Result<(), std::io::Error>`
  = note: this error originates in the attribute macro `::actix_web::rt::main` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0308`.
error: could not compile `wmm` due to previous error

比如下面的例子,main 函数是没有返回值

fn greet_world() {
     let southern_germany = "Grüß Gott!";
     let chinese = "世界,你好";
     let english = "World, hello";
     let regions = [southern_germany, chinese, english];
     for region in regions.iter() {
             println!("{}", &region);
     }
 }

 fn main() {
     greet_world();
 }

为什么 actix_web 需要返回值呢?

难道和 #[actix_web::main] 有关系吗?

图片.png

但是我直接这样,也是可以跑的

#[actix_web::main]
async fn main() {
    async { println!("Hello world"); }.await
}

运行结果:

╰─➤  cargo run
   Compiling wmm v0.1.0 (/Users/ponponon/Desktop/code/me/wmm)
    Finished dev [unoptimized + debuginfo] target(s) in 0.93s
     Running `target/debug/wmm`
Hello world
阅读 2.4k
2 个回答

首先,Rust 中的 Result 是一个枚举,用于返回 Error,而这个是 Rust 错误处理的知识,本身和 actix-web 无关。

Rust 中函数返回的 Error 必须被处理,这一点和 Golang 中不同。

所以 main 之所以需要返回 Result 是因为它函数内部的代码返回了 Result,而 main 自己没有处理,直接返回出去了。

具体 main 为什么要返回 Result,实际上是最后那个 .await 干的,可以把 main 改成这样:

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let server = HttpServer::new(|| {
        App::new()
            .route("/hello", web::get().to(|| async { "Hello World!" }))
            .service(greet)
    })
    .bind(("127.0.0.1", 8080)).unwrap();
    
    let future = server.run();
    future.await
}

然后就会比较清晰地发现,server.run() 返回了一个 Server 对象,而这个对象在使用 await 关键字时,抛出了 Result

在 Rust 中,如果一行代码后面不加 ;,则被当作返回值,这里 await 的返回值就是一个 Result。原因会在下面再讲。

你如果一定不想返回 Result,只需要把 future.await 改写成 future.await.unwrap(); 就行了。这个也是 Rust 的异常处理机制之一,unwrap() 不处理异常,而是在遇到异常时直接 panic

回到上面的 await,这玩意儿为啥会返回 Result?这个问题我这里只能给你简单说明一下方向,因为涉及到了 Rust 的异步原理,有点复杂(我自己也不是非常熟,尴尬)。

简单说,server.run() 会返回一个 Server 对象,这个对象的定义如下:

#[must_use = "Server does nothing unless you `.await` or poll it"]
pub struct Server {
    handle: ServerHandle,
    fut: BoxFuture<'static, io::Result<()>>,
}

看到那个 fut 了没,它是一个 Future 类型智能指针,并且泛型参数是 io::Result<()>

而当 Server 作为一个 Futureawait 的时候,异步运行时会执行 poll,源码如下:

impl Future for Server {
    type Output = io::Result<()>;

    #[inline]
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        Pin::new(&mut Pin::into_inner(self).fut).poll(cx)
    }
}

嗯,大概就是这样。

这是Result的底层定义:

image.png

它是一个泛型枚举, 要么是 Ok, 要么是 Err, 对应到 C 中的返回结果就是 0, 和非 0, 从程序返回语义角度, 表示正确结果和错误结果, 错误结果就应该要处理.

得益于枚举的设计, 枚举值还可以包装复杂类型作为其值, 可以从 Result 拿到正确的返回值或者错误信息. 这样就可以对结果进行 if else 处理.

语言是这么设计的, Swift 里面也有类似的设计, 它的意义在于, 把我们之前写代码返回数字来表示程序是否正常返回结果标准化一个 Result 枚举, 可读性更好, C 风格的返回码完全依赖作者对返回值含义的定义, 当然 C 当中大部分情况是约定俗成的, 但也免不了一些非主流的返回码定义.

Result通过文字而非数字来表示正确和错误, 一看便知!

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进