Rust 中的错误处理 已翻译 100%

xiaoaiwhc1 投递于 2018/04/26 10:46 (共 76 段, 翻译完成于 05-08)
阅读 5795
收藏 32
4
加载中

Like most programming languages, Rust encourages the programmer to handle errors in a particular way. Generally speaking, error handling is divided into two broad categories: exceptions and return values. Rust opts for return values.

In this article, I intend to provide a comprehensive treatment of how to deal with errors in Rust. More than that, I will attempt to introduce error handling one piece at a time so that you’ll come away with a solid working knowledge of how everything fits together.

When done naively, error handling in Rust can be verbose and annoying. This article will explore those stumbling blocks and demonstrate how to use the standard library to make error handling concise and ergonomic.

已有 1 人翻译此段
我来翻译

Target audience: Those new to Rust that don’t know its error handling idioms yet. Some familiarity with Rust is helpful. (This article makes heavy use of some standard traits and some very light use of closures and macros.)

Update (2018/04/14): Examples were converted to ?, and some text was added to give historical context on the change.

Brief notes

All code samples in this post compile with Rust 1.0.0-beta.5. They should continue to work as Rust 1.0 stable is released.

已有 1 人翻译此段
我来翻译

All code can be found and compiled in my blog’s repository.

The Rust Book has a section on error handling. It gives a very brief overview, but doesn’t (yet) go into enough detail, particularly when working with some of the more recent additions to the standard library.

Run the code!

If you’d like to run any of the code samples below, then the following should work:

$ git clone git://github.com/BurntSushi/blog
$ cd blog/code/rust-error-handling
$ cargo run --bin NAME-OF-CODE-SAMPLE [ args ... ]

Each code sample is labeled with its name. (Code samples without a name aren’t available to be run this way. Sorry.)

已有 1 人翻译此段
我来翻译

Table of Contents

This article is very long, mostly because I start at the very beginning with sum types and combinators, and try to motivate the way Rust does error handling incrementally. As such, programmers with experience in other expressive type systems may want to jump around. Here’s my very brief guide:

  • If you’re new to Rust, systems programming and expressive type systems, then start at the beginning and work your way through. (If you’re brand new, you should probably read through the Rust book first.)

  • If you’ve never seen Rust before but have experience with functional languages (“algebraic data types” and “combinators” make you feel warm and fuzzy), then you can probably skip right over the basics and start by skimming multiple error types, and work you’re way into a full read ofstandard library error traits. (Skimming the basics might be a good idea to just get a feel for the syntax if you’ve really never seen Rust before.) You may need to consult the Rust book for help with Rust closures and macros.

  • If you’re already experienced with Rust and just want the skinny on error handling, then you can probably skip straight to the end. You may find it useful to skim the case study for examples.

已有 1 人翻译此段
我来翻译

The Basics

I like to think of error handling as using case analysis to determine whether a computation was successful or not. As we will see, the key to ergonomic error handling is reducing the amount of explicit case analysis the programmer has to do while keeping code composable.

Keeping code composable is important, because without that requirement, we could panic whenever we come across something unexpected. (panic causes the current task to unwind, and in most cases, the entire program aborts.) Here’s an example:

panic-simple

// Guess a number between 1 and 10.
// If it matches the number I had in mind, return true. Else, return false.
fn guess(n: i32) -> bool {
    if n < 1 || n > 10 {
        panic!("Invalid number: {}", n);
    }
    n == 5
}

fn main() {
    guess(11);
}

(If you like, it’s easy to run this code.)

已有 1 人翻译此段
我来翻译

If you try running this code, the program will crash with a message like this:

thread '<main>' panicked at 'Invalid number: 11', src/bin/panic-simple.rs:5

Here’s another example that is slightly less contrived. A program that accepts an integer as an argument, doubles it and prints it.

unwrap-double

use std::env;

fn main() {
    let mut argv = env::args();
    let arg: String = argv.nth(1).unwrap(); // error 1
    let n: i32 = arg.parse().unwrap(); // error 2
    println!("{}", 2 * n);
}

// $ cargo run --bin unwrap-double 5
// 10

If you give this program zero arguments (error 1) or if the first argument isn’t an integer (error 2), the program will panic just like in the first example.

I like to think of this style of error handling as similar to a bull running through a china shop. The bull will get to where it wants to go, but it will trample everything in the process.

已有 1 人翻译此段
我来翻译

Unwrapping explained

In the previous example (unwrap-double), I claimed that the program would simply panic if it reached one of the two error conditions, yet, the program does not include an explicit call to panic like the first example (panic-simple). This is because the panic is embedded in the calls to unwrap.

To “unwrap” something in Rust is to say, “Give me the result of the computation, and if there was an error, just panic and stop the program.” It would be better if I just showed the code for unwrapping because it is so simple, but to do that, we will first need to explore the Option and Result types. Both of these types have a method called unwrap defined on them.

已有 1 人翻译此段
我来翻译

The Option type

The Option type is defined in the standard library:

option-def

enum Option<T> {
    None,
    Some(T),
}

The Option type is a way to use Rust’s type system to express the possibility of absence. Encoding the possibility of absence into the type system is an important concept because it will cause the compiler to force the programmer to handle that absence. Let’s take a look at an example that tries to find a character in a string:

已有 1 人翻译此段
我来翻译

option-ex-string-find

// Searches `haystack` for the Unicode character `needle`. If one is found, the
// byte offset of the character is returned. Otherwise, `None` is returned.
fn find(haystack: &str, needle: char) -> Option<usize> {
    for (offset, c) in haystack.char_indices() {
        if c == needle {
            return Some(offset);
        }
    }
    None
}

(Pro-tip: don’t use this code. Instead, use the find method from the standard library.)

Notice that when this function finds a matching character, it doen’t just return the offset. Instead, it returns Some(offset)Some is a variant or a value constructor for the Option type. You can think of it as a function with the type fn<T>(value: T) -> Option<T>. Correspondingly, None is also a value constructor, except it has no arguments. You can think of None as a function with the type fn<T>() -> Option<T>.

This might seem like much ado about nothing, but this is only half of the story. The other half is usingthe find function we’ve written. Let’s try to use it to find the extension in a file name.

已有 1 人翻译此段
我来翻译

option-ex-string-find

fn main_find() {
    let file_name = "foobar.rs";
    match find(file_name, '.') {
        None => println!("No file extension found."),
        Some(i) => println!("File extension: {}", &file_name[i+1..]),
    }
}

This code uses pattern matching to do case analysis on the Option<usize> returned by the find function. In fact, case analysis is the only way to get at the value stored inside an Option<T>. This means that you, as the programmer, must handle the case when an Option<T> is None instead of Some(t).

But wait, what about unwrap used in unwrap-double? There was no case analysis there! Instead, the case analysis was put inside the unwrap method for you. You could define it yourself if you want:

已有 1 人翻译此段
我来翻译
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(0)

返回顶部
顶部