? in main and tests

Minimum Rust version: 1.26

Rust's error handling revolves around returning Result<T, E> and using ?to propagate errors. For those who write many small programs and, hopefully,many tests, one common paper cut has been mixing entry points such as mainand #[test]s with error handling.

As an example, you might have tried to write:

  1. use std::fs::File;
  2. fn main() {
  3. let f = File::open("bar.txt")?;
  4. }

Since ? works by propagating the Result with an early return to theenclosing function, the snippet above does not work, and results todayin the following error:

  1. error[E0277]: the `?` operator can only be used in a function that returns `Result`
  2. or `Option` (or another type that implements `std::ops::Try`)
  3. --> src/main.rs:5:13
  4. |
  5. 5 | let f = File::open("bar.txt")?;
  6. | ^^^^^^^^^^^^^^^^^^^^^^ cannot use the `?` operator in a function that returns `()`
  7. |
  8. = help: the trait `std::ops::Try` is not implemented for `()`
  9. = note: required by `std::ops::Try::from_error`

To solve this problem in Rust 2015, you might have written something like:

  1. // Rust 2015
  2. use std::process;
  3. use std::error::Error;
  4. fn run() -> Result<(), Box<Error>> {
  5. // real logic..
  6. Ok(())
  7. }
  8. fn main() {
  9. if let Err(e) = run() {
  10. println!("Application error: {}", e);
  11. process::exit(1);
  12. }
  13. }

However, in this case, the run function has all the interesting logic andmain is just boilerplate. The problem is even worse for #[test]s, sincethere tend to be a lot more of them.

In Rust 2018 you can instead let your #[test]s and main functions returna Result:

  1. // Rust 2018
  2. use std::fs::File;
  3. fn main() -> Result<(), std::io::Error> {
  4. let f = File::open("bar.txt")?;
  5. Ok(())
  6. }

In this case, if say the file doesn't exist and there is an Err(err) somewhere,then main will exit with an error code (not 0) and print out a Debugrepresentation of err.

More details

Getting -> Result<..> to work in the context of main and #[test]s is notmagic. It is all backed up by a Termination trait which all valid returntypes of main and testing functions must implement. The trait is defined as:

  1. #![allow(unused_variables)]
  2. fn main() {
  3. pub trait Termination {
  4. fn report(self) -> i32;
  5. }
  6. }

When setting up the entry point for your application, the compiler will use thistrait and call .report() on the Result of the main function you have written.

Two simplified example implementations of this trait for Result and () are:

  1. #![allow(unused_variables)]
  2. fn main() {
  3. #![feature(process_exitcode_placeholder, termination_trait_lib)]
  4. use std::process::ExitCode;
  5. use std::fmt;
  6. pub trait Termination { fn report(self) -> i32; }
  7. impl Termination for () {
  8. fn report(self) -> i32 {
  9. use std::process::Termination;
  10. ExitCode::SUCCESS.report()
  11. }
  12. }
  13. impl<E: fmt::Debug> Termination for Result<(), E> {
  14. fn report(self) -> i32 {
  15. match self {
  16. Ok(()) => ().report(),
  17. Err(err) => {
  18. eprintln!("Error: {:?}", err);
  19. use std::process::Termination;
  20. ExitCode::FAILURE.report()
  21. }
  22. }
  23. }
  24. }
  25. }

As you can see in the case of (), a success code is simply returned.In the case of Result, the success case delegates to the implementation for() but prints out an error message and a failure exit code on Err(..).

To learn more about the finer details, consult either the tracking issue or the RFC.