为什么在不返回Option或Result的函数中使用try!()和?会无法编译?

67

为什么这段代码不能编译?

use std::{fs, path::Path};

fn main() {
    let dir = Path::new("../FileSystem");

    if !dir.is_dir() {
        println!("Is not a directory");
        return;
    }

    for item in try!(fs::read_dir(dir)) {
        let file = match item {
            Err(e) => {
                println!("Error: {}", e);
                return;
            }
            Ok(f) => f,
        };

        println!("");
    }

    println!("Done");
}

这是我遇到的错误

error[E0308]: mismatched types
  --> src/main.rs:11:17
   |
11 |     for item in try!(fs::read_dir(dir)) {
   |                 ^^^^^^^^^^^^^^^^^^^^^^^ expected (), found enum `std::result::Result`
   |
   = note: expected type `()`
              found type `std::result::Result<_, _>`
   = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

我也尝试了问号运算符:

for item in fs::read_dir(dir)? {

有一个不同的错误:

error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `std::ops::Try`)
  --> src/main.rs:11:17
   |
11 |     for item in fs::read_dir(dir)? {
   |                 ^^^^^^^^^^^^^^^^^^ cannot use the `?` operator in a function that returns `()`
   |
   = help: the trait `std::ops::Try` is not implemented for `()`
   = note: required by `std::ops::Try::from_error`

以前的 Rust 版本中也有一个类似于 std::ops::Carrier 的错误。

我应该避免使用 try!()? 吗?处理错误的最佳方式是什么?大多数情况下,我会这样做:

match error_prone {
    Err(e) => {
        println!("Error: {}", e);
        return;
    },
    Ok(f) => f,
};

但是如果我必须在 for 循环中使用它,那就会变得一团糟

for i in match error_prone {
    // match code
} {
    // loop code
}

7
try! 是一个宏,可以自动返回 Err。由于 main 函数没有返回值,所以不能在该函数中使用 try! - Veedrac
请参见链接:http://lucumr.pocoo.org/2014/11/6/error-handling-in-rust/通常来说,简单的方法是将你的代码放在一个包装函数中,并从main中调用它,该函数返回 Result<T, Box<Error>>,然后在main中使用 unwrap 或处理它。 - Veedrac
"println!("{}", path.unwrap().path().file_name().unwrap().to_str().unwrap());" 真的吗? :p - Mathieu David
path 是什么类型? - Veedrac
由于所有函数都返回Result<something>,我很难知道如何回答你的问题。但是,文件在Github上,这是read_dir的结果。 - Mathieu David
显示剩余2条评论
4个回答

65

try! 是一个宏,自动返回 Err? 是一种语法,几乎可以实现相同的功能,但它适用于任何实现了 Try 特征的类型。

Rust 1.22.0 开始,Option 实现了 Try,因此可以与 ? 一起使用。在那之前,? 只能在返回 Result 的函数中使用。try! 仍然只适用于 Result

Rust 1.26.0 开始,main 允许返回一个实现了 Termination 的值。在此之前,它不返回任何值。

从 Rust 1.26.0 开始

如果您将 main 标记为返回 Result 并在所有“成功”情况下返回 Ok(()),则您的原始代码可以正常工作:

use std::{fs, io, path::Path};

fn main() -> Result<(), io::Error> {
    let dir = Path::new("../FileSystem");

    if !dir.is_dir() {
        println!("Is not a directory");
        return Ok(());
    }

    for item in fs::read_dir(dir)? {
        let file = match item {
            Err(e) => {
                println!("Error: {}", e);
                return Ok(());
            }
            Ok(f) => f,
        };

        println!("");
    }

    println!("Done");
    Ok(())
}

在此之前

以下是如何修改您的代码以使用?

use std::{error::Error, fs, path::Path};

fn print_dir_contents() -> Result<String, Box<Error>> {
    let dir = Path::new("../FileSystem");

    if !dir.is_dir() {
        return Err(Box::from("Is not a directory!"));
    }

    for entry in fs::read_dir(dir)? {
        let path = entry?.path();
        let file_name = path.file_name().unwrap();
        println!("{}", file_name.to_string_lossy());
    }

    Ok("Done".into())
}

fn main() {
    match print_dir_contents() {
        Ok(s) => println!("{}", s),
        Err(e) => println!("Error: {}", e.to_string()),
    }
}

这里有很多错误处理,可能会令你感到意外 - 其他语言不太需要它们!但其他语言中也存在这些错误 - Rust只是让你知道了它们。以下是这些错误:

entry?

在迭代过程中可能会发生IO错误。

path.file_name().unwrap()

并不是所有路径都有文件名。我们可以对此进行解包,因为read_dir不会给我们一个没有文件名的路径。

file_name.to_string_lossy()
您也可以使用to_str并抛出一个错误,但最好这样做。这个错误的存在是因为并非所有文件名都是有效的Unicode。 try!?将错误抛入返回值中,并将它们转换为Box::Error。实际上,更合理的做法是返回所有可能出错的问题的汇总错误。幸运的是,io::Error正是正确的类型。
use std::io;

// ...

fn print_dir_contents() -> Result<String, io::Error> {
    // ...

    if !dir.is_dir() {
        return Err(io::Error::new(io::ErrorKind::Other, "Is not a directory!"));
    }

    // ...
}

坦率地说,这项检查已经包含在fs::read_dir中,因此您可以直接完全删除if !dis.is_dir:
use std::{fs, io, path::Path};

fn print_dir_contents() -> Result<String, io::Error> {
    let dir = Path::new("../FileSystem");

    for entry in fs::read_dir(dir)? {
        let path = entry?.path();
        let file_name = path.file_name().unwrap();
        println!("{}", file_name.to_string_lossy());
    }

    Ok("Done".into())
}

fn main() {
    match print_dir_contents() {
        Ok(s) => println!("{}", s),
        Err(e) => println!("Error: {}", e.to_string()),
    }
}

为了让 ? 操作符对其他类型的错误也有效,我需要改为 fn main() -> Result<(), Box<std::error::Error>>> {。更多信息请阅读 https://blog.burntsushi.net/rust-error-handling/#the-real-try-macro-operator - rofrol

7
最近,ques_in_main RFC被合并了。一旦它完成,问题中的语法确实会编译正常并按预期工作,只要将try!()调用替换为?运算符即可。该RFC的链接为:ques_in_main RFC,完成状态的链接为:completed

3
自Rust 1.26起,Rust支持从main()返回值,并且支持在main()中使用错误检查运算符?(或等效的try!()宏),当main()被定义为返回Result时:
extern crate failure;
use failure::Error;
use std::fs::File;

type Result<T> = std::result::Result<T, Error>;

fn main() -> Result<()> {
    let mut _file = File::open("foo.txt")?; // does not exist; returns error
    println!("the file is open!");
    Ok(())
}

以上代码编译并返回一个文件未找到的错误(假设foo.txt在本地路径中不存在)。

Rust playground示例


1

Veedrac的回答对我也有帮助,尽管OP的问题略有不同。在阅读Rust文档时,我看到了这段代码片段:

use std::fs::File;
use std::io::prelude::*;

let mut file = File::open("foo.txt")?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
assert_eq!(contents, "Hello, world!");

尽管在 Rust 书中指出主函数的重要性,但如果在其中运行此代码,则会收到类似的错误。如果将代码包装在处理错误的函数中,则上述片段将起作用:
use std::error::Error;
use std::io::prelude::*;
use std::fs::File;

fn print_file_content() -> Result<String, Box<Error>> {
    let mut f = File::open("foo.txt")?;
    let mut contents = String::new();

    f.read_to_string(&mut contents)?;

    println!("The content: {:?}", contents);

    Ok("Done".into())
}

fn main() {
    match print_file_content() {
        Ok(s) => println!("{}", s),
        Err(e) => println!("Error: {}", e.to_string()),
    }
}

附言:我正在学习Rust,因此这些代码片段并不意味着是良好的Rust编码 :)


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