Lỗi có thể khôi phục với Result
Hầu hết các lỗi không nghiêm trọng lắm thường không yêu cầu chương trình chúng ta dừng lại.
Thỉnh thoảng, khi một hàm nào đó gặp lỗi vì một vài lý do nào đó mà bạn có thể dễ dàng giải thích và xử lý nó. Ví dụ, nếu bạn cố gắng mở một file và điều khiển đó bị lỗi bởi vì file không tồn tại, bạn có thể muốn tạo file thay vì kết thúc.
Nhắc lại từ “Handling Potential Failure with the Result
Type” trong chương 2 mà Result
enum là được định nghĩa như có 2 biến thể : Ok
hoặc Err
như sau:
#![allow(unused)] fn main() { enum Result<T, E> { Ok(T), Err(E), } }
T
và E
là các tham số với kiểu dữ liệu tổng quát (generic), chúng ta sẽ thảo luận về generic
chi tiết hơn trong chương 10. Gì chúng ta biết về nó đến này là T
thể hiện kiểu của dữ liệu sẽ đuợc trả về trong trường hợp thành công trong Ok
, và E
thể hiện kiểu của lỗi mà được trở về trong trường hợp lỗi với biến thể Err
. Bởi vì Result
có các tham số kiểu generic này, chúng ta có thể sử dụng kiểu Result
và các hàm của nó định nghĩa trong nhiều trường hợp khác nhau nơi giá trị thành công hoặc giá trị lỗi có thể nhận các kiểu khác nhau.
Cùng xem xét việc gọi một hàm mà trở về một kiểu Result
bởi vì hàm có thể lỗi. Trong Listing 9-3 chúng ta cố gắng mở một file
Filename: src/main.rs
use std::fs::File; fn main() { let f = File::open("hello.txt"); }
Cách mà chúng ta biết File::open
trở về một kiểu Result
?. Chúng có thể xem standard
library API documentation, hoặc chúng có thể yêu cầu trình biên dịch (compiler). Nếu chúng đưa f
một ký annotation
mà chúng ta biết là không phải là giá trị trở kiểu hàm và sau đó cố gắng để biên dịch, trình biên dịch sẽ nói với chúng ta thằng kiểu này sẽ không phù hợp (match). Thông điệpj lỗi sẽ nói với chunts ta kiểu giữ liệu nào f
là phù hợp. Cùng thử ví dụ sau: Kiểu File::open
không phải là u32
, nếu thay đổi let
f` tới
use std::fs::File;
fn main() {
let f: u32 = File::open("hello.txt");
}
Cố gắng để biên dịch đưa chúng ta đầu ra như sau:
$ cargo run
Compiling error-handling v0.1.0 (file:///projects/error-handling)
error[E0308]: mismatched types
--> src/main.rs:4:18
|
4 | let f: u32 = File::open("hello.txt");
| --- ^^^^^^^^^^^^^^^^^^^^^^^ expected `u32`, found enum `Result`
| |
| expected due to this
|
= note: expected type `u32`
found enum `Result<File, std::io::Error>`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `error-handling` due to previous error
Điều này nói với chúng ta kiểu trả về của hàm File::open
là Result<T,E>
Tham số generic T
đã được chỉ ra ở đây với kiểu giá trị thành công là std::fs::File
, mà là một handle của 1 file (Dạng tham chiếu 1 file).
Kiểu E
được sử dụng trong giá trị lỗi là std::io::Error
.
Kiểu giá trị trả về nghĩa là gọi File::open
có thể thành công và trở về handle của file mà bạn có thể đọc hoặc ghi tới. Lời gọi hàm cũng có thể lỗi: Ví dụ, file có thể không tồn tại, hoặc chúng ta có thể không có quyền để truy xuất file. Hàm File::opne
cần để có cách để nói với chúng ta liệu nó là thành công hoặc thất bại cùng lúc đưa chúng ta hoặc handle của file hoặc thông tin lỗi. Thông tin này chính là những gì Result
enum truyền tải.
Trong trường hợp File::open
thành công, giá trị trong biến f
sẽ là thực thể của Ok
mà bao gồm một file handle. Trong trường hợp lỗi, giá trị f
là một thực thể Err
mà bao gồm nhiều thông tin về kiểu lỗi đã xảy ra.
Chúng ta có thể thêm code trong Listing 9-3 để xử lý kiểu File::open
trả về. Trong Listing 9-4 chỉ ra một cách để xử lý Result
cơ bản, cú pháp match
chúng ta đã thảo luận trong Chương 6.
Filename: src/main.rs
use std::fs::File; fn main() { let f = File::open("hello.txt"); let f = match f { Ok(file) => file, Err(error) => panic!("Problem opening the file: {:?}", error), }; }
Chú ý rằng, giống Option
enum, kiểu Result
enum và biến thể của nó có thể khai báo khi import, do đó chúng ta không cần chi tiết Result::
trước Ok
và Err
trong cú pháp match
.
Khi kết quả là Ok
, code sẽ trở giá trị file
và sau đó chúng gán file handle tới biến f
. Sau hàm match
, chúng ta có thể sử dụng file handle cho việc đọc hoặc ghi.
Một nhánh khác của match
xử lý trường hợp chúng ta nhận một giá trị Err
từ
File::open
. Trong trường hợp này, chúng ta gọi macro panic!
. Nếu không có file nào tên hello.txt trong đường dẫn hiện tại. Nếu chúng ta chạy code sau , chúng ta sẽ nhìn thấy đầu ra như sau nơi mà macro panic!
được gọi
$ cargo run
Compiling error-handling v0.1.0 (file:///projects/error-handling)
Finished dev [unoptimized + debuginfo] target(s) in 0.73s
Running `target/debug/error-handling`
thread 'main' panicked at 'Problem opening the file: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:8:23
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Thông thường, đàu ra nói với chúng ta chính xác lỗi gì.
Xử lý các lỗi khác nhau
Đoạn code trong Listing 9-4 sẽ panic!
không quan trọng là File::open
lỗi gì. Tuy nhiên, nếu chúng ta muốn xử lý khác nhau cho mỗi kiểu lý do lỗi. Nếu File::opem
lỗi bởi vì file không tồn tại, chúng ta muốn tạo file và trở về một handle của một file mới. Nếu File::open
lỗi cho bất kỳ lý do nào khác, ví như chúng ta không có quyền để mở một file chúng ta vẫn muốn code panic
như chúng ta đã làm trong Listing 9-4. Ví dụ 9-5 sau thêm nhánh của match
:
Filename: src/main.rs
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("Problem creating the file: {:?}", e),
},
other_error => {
panic!("Problem opening the file: {:?}", other_error)
}
},
};
}
Kiểu của giá trị trả về File::open
trong Err
là io::Error
, nới là một cấu được cung cấp bởi thư viện chuẩn. Cấu trúc này có một phương thức kind
mà chúng ta có thể gọi để nhận giá trị io::ErrorKind
.
Enum io::ErrorKind
được cung cấp bởi thư viện chuẩn và có biến thể thể hiện kiểu khác nhau của lỗi mà có thể trả về từ điều khiển io
. BIến thể chúng ta muốn sử dụng ở đây là ErrorKind::NotFound
, mà chỉ file chúng ta đang cố gắng để mở nhưng không tồn tại. Do đó Chúng ta, xử lý f
, nhúng chúng cũng có kiểu trong error.kind()
Điều kiện chúng ta muốn kiểm tra trong cú pháp match
là liệu giá trị trở bởi error.kind()
là NotFound
- một biến thể của ErrorKind
enum. Nếu đúng như vậy, chúng ta cố để tạo một file với File::create
. Tuy nhiên, bởi vì File::create
cũng có thể lỗi, chúng cần nhánh kiểm tra thứ 2 của match
. Khi file không được tạo, một thông điệp lỗi khác sẽ được in ra. Nhánh thứ 2 của match
sẽ được gọi nếu bất kỳ lỗi bên lỗi mising file
(không tìm thấy file).
Thay đổi sử dụng cú pháp
match
vớiResult<T, E>
Biểu thức
match
khá không những hữu dụng mà còn khá làprimitive
(dạng nguyên bản). Trong chương 13, bạn sẽ học vềclosures
, nơi được sử dụng với nhiều phương thức được định nghĩa trongResult<T,E>
. Những phướng thức này có thể ngắn gọn hơn sử dụngmatch
khi xử lý giá trịResult<T, E>
trong code của bạn 9-5 but using closures and theunwrap_or_else
method: Eg, đây là một cách khác để viết cùng một logic như vị dụ trong Listing 9-5 nhưng sử dụng closure và phương thứcunwrap_or_else
:use std::fs::File; use std::io::ErrorKind; fn main() { let f = File::open("hello.txt").unwrap_or_else(|error| { if error.kind() == ErrorKind::NotFound { File::create("hello.txt").unwrap_or_else(|error| { panic!("Problem creating the file: {:?}", error); }) } else { panic!("Problem opening the file: {:?}", error); } }); }
Mặc dù đoạn code này có cùng hành vi như Listing 9-5, nó không bao gồm biểu thức
match
và dễ dàng để đọc hơn. Quay lại ví dụ này sau khi bạn đã học Chương 13, và tìm kiếm phương thứcunwrap_or_else
trong tài liệu thư viện chuẩn. Nhiều phương thức khác có thể được sử dụng vớimatch
bên trong khi bạn xử lý với lỗi.
Xử lý panic của lỗi với: unwrap
and expect
Sử dụng match
cho các trường hợp này khá tốt, nhưng nó có thể dài dòng và không phải lúc nào hiểu ý định nó truyền tải. Kiểu Result<T,E>
có nhiều phương thức trợ giúp được định nghĩa để làm cho nhiều dạng mục đích khác nhau. Phương thức unwrap
là một phương thức thực hiện cú pháp dạng match
chúng ta đã viết trong Listing 9-4. Nếu giá trị Result
là Ok
, unwrap
sẽ trở về gía trị
nằm trong Ok
. Nếu Result
là kiểu Err
, unwrap
sẽ gọi macro panic!
cho chúng ta. Đây là một ví dụ sử dụng unwrap
Filename: src/main.rs
use std::fs::File; fn main() { let f = File::open("hello.txt").unwrap(); }
Nếu chúng ta chạy đoạn code này ngoài một file hello.txt, chúng ta sẽ nhìn thấy một thông điệp lỗi từ panic!
được gọi do phương thức unwrap
thực hiện.
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error {
repr: Os { code: 2, message: "No such file or directory" } }',
src/libcore/result.rs:906:4
Một cách tương tự, phương thức expect
để chúng ta lựa chọn thông điệp cho panic!
.
Sử dụng expect
thay unwrap
và cung cấp một thông điệp lỗi rõ ràng có thể truyền tải ý định
và làm cho dễ dò lỗi đến từ đâu. Cú pháp cho expect
như sau:
Filename: src/main.rs
use std::fs::File; fn main() { let f = File::open("hello.txt").expect("Failed to open hello.txt"); }
Chúng ta sử dụng expect
trong cùng một cách với unwrap
: trở về một file handle hoặc gọi
macro panic!
. Thông điệp lỗi được sử dụng bởi expect
trong lời gọi hàm tới panic!
sẽ là tham
số truyền vào expect
, thay thế thông điệp panic!
mặc định mà unwrap
sử dụng.
thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code:
2, message: "No such file or directory" } }', src/libcore/result.rs:906:4
Bởi vì thông điệp lỗi này bắt đầu đoạn text mà chúng ta truyền vào Failed to open hello.txt
, nó sẽ dễ dàng để tìm kiếm nơi thông điệp lỗi in ra. Nếu chúng ta sử dụng unwrap
trong nhiều nơi, sẽ tốn nhiều thời gian để xác định chính xác unwrap
nào gây ra lỗi panic bởi
tất cả lời gọi unwrap
in ra cùng một thông điệp giống nhau.
Lan Truyền Lỗi - Propagating Errors
Khi thực hiện của một hàm gọi một điều gì đó mà có lỗi, thay vì xử lý lỗi trong hàm này, bạn có thể trả về lỗi tới hàm gọi để hàm này có thể quyết định nên làm gì. Đó được gọi là propagating (lan truyền) lỗi và đưa nhiều điều khiển tới lời gọi hàm nơi có thể có nhiều thông tin hoặc logic để ra lệnh lỗi nên được xử lý hơn là gì trong ngữ cảnh code hiện tại.
Ví du, Listing 9-6 chỉ ra một hàm mà đọc một username từ một file. Nếu như file không tồn tài hoặc không thể đọc, hàm này sẽ trở về những lỗi này tới code mà gọi hàm.
Filename: src/main.rs
#![allow(unused)] fn main() { use std::fs::File; use std::io::{self, Read}; fn read_username_from_file() -> Result<String, io::Error> { let f = File::open("hello.txt"); let mut f = match f { Ok(file) => file, Err(e) => return Err(e), }; let mut s = String::new(); match f.read_to_string(&mut s) { Ok(_) => Ok(s), Err(e) => Err(e), } } }
Hàm này có thể được viết trong nhiều cách ngắn gọn hơn, nhưng chúng ta hãy cứ bắt đầu làm vậy để có thể khám phá cách xử lý lỗi; cuối cùng, chúng ta sẽ chỉ cách làm nó ngắn gọn. Hãy nhìn và kiểu trả về của hàm: Result<String, io::Error>
. Điều này nghĩa rằng hàm là đang trả về giá trị của kiểu Result<T, E>
, nơi mà T
là String
và kiểu tổng quát E
là io::Error
.
Nếu hàm này thành công ngoại trừ bất kỳ lỗi nào, đoạn code mà gọi hàm này sẽ nhận giá trị Ok
mà giữ giá trị String
- một username mà hàm này đọc được. Nếu hàm này chạm trán lỗi bất kỳ, lời gọi hàm sẽ nhậ giá trị Err
mà giữ một instance của io::Error
mà bao gồm nhiều thông tin về lỗi gì. Chúng ta chọn io::Error
như kiểu trả về của hàm này bởi vì kiểu xảy ra với điều khiển File::open
với điều khiển gọi hàm này read_to_string
cùng trả về kiểu này trong khi lỗi.
Thân hàm bắt đầu bởi gọi File::open
. Sau đó, chúng ta xử lý Result
với cú pháp match
tương tự với ví dụ Listing 9-4.
Nếu File::open
thành công, file handle lưu giữ trong biến f
và hàm tiếp tục chạy. Trong trường hợp Err
lỗi, thay vì gọi panic!
, chúng ta sử dụng từ khóa return
để thoát khỏi hàm sớm và trả lỗi từ File::open
ra ngoài,, nơi mà đoạn code gọi hàm của chúng ta.
Do đó, nếu chúng ta có file handle trong f
, hàm sau đó sẽ tạo một String
mới trong biến s
và gọi tới phương thức read_to_string
từ f để đọc nội dùng của file vào s
. Phương thức
read_to_string
cũng trả về Result
bởi vì nó có thể lỗi, mặc dù File::open
thành công.
Do đó chúng ta cần match
khác để xử lý Result
: Nếu read_to_string
thành công, hàm của chúng đã thành công, và chúng ta trả về username từ một file được bọc trong Ok
enum. Nếu read_to_string
lỗi, chúng ta trả về giá trị lỗi trong cách giống chúng ta trả về giá trị lỗi khi xử lý File::open
. Tuy nhiên, chúng ta không cần viết chính xác return
, bởi đó là biểu thức cuối cùng của hàm rồi.
Đoạn code mà gọi hàm này sẽ xử lý hoặc Ok
mà bao gồm username, hoặc là Err
mà bao gồm
io::Error
. Nó phụ thuộc đoạn code gọi hàm quyết định sẽ làm gì với các giá trị này. Nếu đoạn code nhận một giá trị Err
, nó có thể gọi panic!
và crash chương trình, sử dụng username mặc định, hoặc tìm kiềm username trong một file khác chẳng hạn. Chúng ta không đủ thông tin về đoạn code gọi là thực sự đang cố gắng làm, do đó chúng lan truyền tất cả thông tin thành hoặc lỗi tới chúng để xử lý một cách chính xác.
Mẫu cho lan truyền lỗi là khá thông dụng trong Rust và Rust cũng cung cấp một toán tử ?
để làm điều này dễ dàng hơn
Cú pháp làn truyền lỗi : Toán tử ?
Listing 9-7 là thực hiện của hàm read_username_from_file
mà có cùng chức năng với Listing 9-6, nhưng nó sử dụng toán tử ?
Filename: src/main.rs
#![allow(unused)] fn main() { use std::fs::File; use std::io; use std::io::Read; fn read_username_from_file() -> Result<String, io::Error> { let mut f = File::open("hello.txt")?; let mut s = String::new(); f.read_to_string(&mut s)?; Ok(s) } }
?
được đặc sau giá trị Result
được định nghĩa để làm hầu như giống cách trong match
mà chúng ta đã xử lý giá trị Result
trong Listing 9-6. Nếu giá trị Result
là Ok
, giá trị trong Ok
sẽ trả về từ biểu thức này và chương trình sẽ tiếp tục. Nếu giá trị trả về là Err
,
Err
sẽ trả về khỏi hàm giống như đã sử dụng từ khóa return
,
Có một sử khác nhau giữa match
từ ví dụ Listing 9-6 làm và toán tử ?
thực hiện: giá trị lỗi mà ?
đã gọi chạy qua hàm from
, được định nghĩa trong trait From
trong thư viện chuẩn, mà sử dụng để biến đổi lỗi từ kiểu này sang kiểu khác. Khi toán tử ? gọi hàm
from, kiểu lỗi nhận được là biến thành lỗi định trong kiểu dữ liệu trả về hàm của chúng ta. Điều này là hữu ích khi một hàm trở về một lỗi mà thể hiện tất cả cách có thể lỗi, ngay cả một phần của nó lỗi cho nhiều lý do nào đó. Miễn là có thực hiện
impl From for ReturnError
để định nghĩa sự biến đổi trong hàm from
, toán tử ?
lo việc gọi hàm from
một cách tự động cho bạn.
Trong ngữ cảnh Listing 9-7, ?
gọi tại cuối File::open
sẽ trở về giá trị trong Ok
tới biến f
. Nếu một lỗi xảy ra, toán tử ?
sẽ trả trả về sớm và đưa Err
tới lời gọi hàm. Hành vi giống nhau áp dụng tới ?
tại nơi gọi hàm read_to_string
.
Toán tử ?
vứt bỏ nhiều thủ tục (boilerplate) và làm cho thực hiện hàm trở nên đơn giản. Chúng thậm chí có thể gọi liên tiếp ?
cho đoạn code ngắn gọn hơn như chỉ ra trong Listing 9-8.
Filename: src/main.rs
#![allow(unused)] fn main() { use std::fs::File; use std::io; use std::io::Read; fn read_username_from_file() -> Result<String, io::Error> { let mut s = String::new(); File::open("hello.txt")?.read_to_string(&mut s)?; Ok(s) } }
Chúng ta tạo một String
mới tới s
như bắt đầu hàm. Thay vì tạo biến f
, chúng ta chain (nối) lời gọi tới read_to_string
trực tiếp kết quả của File::open("hello.txt")?
. Chúng ta vẫn có ? tại cuối của
read_to_string, và chúng ta vẫn trả về giá trị
Ok`` bao gồm username trong s
khi cả File::open
và read_to_string
thành công. Hàm này làm cùng cách với Listing 9-=6 và Listing 9-7; nhưng viết theo một cách khá hữu hiệu.
Listing 9-9 chỉ ra một cách để làm ngắn gọn hàm fs::read_to_string
.
Filename: src/main.rs
#![allow(unused)] fn main() { use std::fs; use std::io; fn read_username_from_file() -> Result<String, io::Error> { fs::read_to_string("hello.txt") } }
Reading a file into a string is a fairly common operation, so the standard
library provides the convenient fs::read_to_string
function that opens the
file, creates a new String
, reads the contents of the file, puts the contents
into that String
, and returns it. Of course, using fs::read_to_string
doesn’t give us the opportunity to explain all the error handling, so we did it
the longer way first.
Where The ?
Operator Can Be Used
The ?
operator can only be used in functions whose return type is compatible
with the value the ?
is used on. This is because the ?
operator is defined
to perform an early return of a value out of the function, in the same manner
as the match
expression we defined in Listing 9-6. In Listing 9-6, the
match
was using a Result
value, and the early return arm returned an
Err(e)
value. The return type of the function has to be a Result
so that
it’s compatible with this return
.
In Listing 9-10, let’s look at the error we’ll get if we use the ?
operator
in a main
function with a return type incompatible with the type of the value
we use ?
on:
use std::fs::File;
fn main() {
let f = File::open("hello.txt")?;
}
This code opens a file, which might fail. The ?
operator follows the Result
value returned by File::open
, but this main
function has the return type of
()
, not Result
. When we compile this code, we get the following error
message:
$ cargo run
Compiling error-handling v0.1.0 (file:///projects/error-handling)
error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
--> src/main.rs:4:36
|
3 | / fn main() {
4 | | let f = File::open("hello.txt")?;
| | ^ cannot use the `?` operator in a function that returns `()`
5 | | }
| |_- this function should return `Result` or `Option` to accept `?`
|
= help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()`
For more information about this error, try `rustc --explain E0277`.
error: could not compile `error-handling` due to previous error
This error points out that we’re only allowed to use the ?
operator in a
function that returns Result
, Option
, or another type that implements
FromResidual
. To fix the error, you have two choices. One choice is to change
the return type of your function to be compatible with the value you’re using
the ?
operator on as long as you have no restrictions preventing that. The
other technique is to use a match
or one of the Result<T, E>
methods to
handle the Result<T, E>
in whatever way is appropriate.
The error message also mentioned that ?
can be used with Option<T>
values
as well. As with using ?
on Result
, you can only use ?
on Option
in a
function that returns an Option
. The behavior of the ?
operator when called
on an Option<T>
is similar to its behavior when called on a Result<T, E>
:
if the value is None
, the None
will be returned early from the function at
that point. If the value is Some
, the value inside the Some
is the
resulting value of the expression and the function continues. Listing 9-11 has
an example of a function that finds the last character of the first line in the
given text:
fn last_char_of_first_line(text: &str) -> Option<char> { text.lines().next()?.chars().last() } fn main() { assert_eq!( last_char_of_first_line("Hello, world\nHow are you today?"), Some('d') ); assert_eq!(last_char_of_first_line(""), None); assert_eq!(last_char_of_first_line("\nhi"), None); }
This function returns Option<char>
because it’s possible that there is a
character there, but it’s also possible that there isn’t. This code takes the
text
string slice argument and calls the lines
method on it, which returns
an iterator over the lines in the string. Because this function wants to
examine the first line, it calls next
on the iterator to get the first value
from the iterator. If text
is the empty string, this call to next
will
return None
, in which case we use ?
to stop and return None
from
last_char_of_first_line
. If text
is not the empty string, next
will
return a Some
value containing a string slice of the first line in text
.
The ?
extracts the string slice, and we can call chars
on that string slice
to get an iterator of its characters. We’re interested in the last character in
this first line, so we call last
to return the last item in the iterator.
This is an Option
because it’s possible that the first line is the empty
string, for example if text
starts with a blank line but has characters on
other lines, as in "\nhi"
. However, if there is a last character on the first
line, it will be returned in the Some
variant. The ?
operator in the middle
gives us a concise way to express this logic, allowing us to implement the
function in one line. If we couldn’t use the ?
operator on Option
, we’d
have to implement this logic using more method calls or a match
expression.
Note that you can use the ?
operator on a Result
in a function that returns
Result
, and you can use the ?
operator on an Option
in a function that
returns Option
, but you can’t mix and match. The ?
operator won’t
automatically convert a Result
to an Option
or vice versa; in those cases,
you can use methods like the ok
method on Result
or the ok_or
method on
Option
to do the conversion explicitly.
So far, all the main
functions we’ve used return ()
. The main
function is
special because it’s the entry and exit point of executable programs, and there
are restrictions on what its return type can be for the programs to behave as
expected.
Luckily, main
can also return a Result<(), E>
. Listing 9-12 has the
code from Listing 9-10 but we’ve changed the return type of main
to be
Result<(), Box<dyn Error>>
and added a return value Ok(())
to the end. This
code will now compile:
use std::error::Error;
use std::fs::File;
fn main() -> Result<(), Box<dyn Error>> {
let f = File::open("hello.txt")?;
Ok(())
}
The Box<dyn Error>
type is a trait object, which we’ll talk about in the
“Using Trait Objects that Allow for Values of Different
Types” section in Chapter 17. For now, you can
read Box<dyn Error>
to mean “any kind of error.” Using ?
on a Result
value in a main
function with the error type Box<dyn Error>
is allowed,
because it allows any Err
value to be returned early.
When a main
function returns a Result<(), E>
, the executable will
exit with a value of 0
if main
returns Ok(())
and will exit with a
nonzero value if main
returns an Err
value. Executables written in C return
integers when they exit: programs that exit successfully return the integer
0
, and programs that error return some integer other than 0
. Rust also
returns integers from executables to be compatible with this convention.
The main
function may return any types that implement the
std::process::Termination
trait. As of this
writing, the Termination
trait is an unstable feature only available in
Nightly Rust, so you can’t yet implement it for your own types in Stable Rust,
but you might be able to someday!
Now that we’ve discussed the details of calling panic!
or returning Result
,
let’s return to the topic of how to decide which is appropriate to use in which
cases.