Is it possible to implement a method that takes a format string?

Viewed 587

I've found myself using .expect("...") to panic with an helpful error message in a lot of places where I don't care about recovering from an error. Example:

let p = "foo.txt";
let f = File::open(p).expect("File not found");

However, I would like to print more information by using a formatted string. This is what I could do:

let f = File::open(p).expect(&format("{} not found", p));

This has two problems:

  1. The format call will be evaluated eagerly.

  2. It's unnecessarily verbose.

Ideally, I would like to write:

// pseudocode
let f = File::open(p).expect_fmt("{} not found", p);

But I suppose that's not possible without variadic generic functions and compile-time parsing of strings.

The only alternative I've found is the following:

let f = File::open(p).unwrap_or_else(|_| panic!("{} not found", p));

Which is still a little bit too verbose for my taste.

It's acceptable if the answer uses nightly Rust.

2 Answers

TL;DR: No.


While there's not much to be done about reducing your verbosity issues, you can avoid the memory allocation by using std::fmt::Arguments

trait ResultExt<T, E> {
    fn expect_fmt<D>(self, msg: D) -> T
    where
        D: std::fmt::Display;
}

impl<T, E> ResultExt<T, E> for Result<T, E>
where
    E: std::error::Error,
{
    fn expect_fmt<D>(self, msg: D) -> T
    where
        D: std::fmt::Display,
    {
        match self {
            Ok(t) => t,
            Err(e) => panic!("{}: {}", msg, e),
        }
    }
}

use std::fs::File;

fn main() {
    let p = "foo.txt";
    let f = File::open(p).expect_fmt(format_args!("{} not found", p));
}

Feel free to adjust the trait bounds as you see fit.

not possible without variadic generic functions and compile-time parsing of strings

That's exactly what the format_args macro does, but you still have to call it.

For avoiding eagerly formatting of the error message a closure may be used, toghether with a simple macro for reducing verbosity a little bit:

macro_rules! crash {
    ( $( $p:tt ),* ) => {
        |_| panic!($($p),*);
    };
}

then the statement sugarizes as:

    let f = File::open(p).unwrap_or_else(crash!("{} not found", p));

No much added value with this approach.

Have you considered error-chain or failure as alternatives for your error management design?

They have a lot to offer for effective error handling.

Using a simple macro, you should anyway tweak creatively the syntax to get near your desiderata, below an error-chain based example:

macro_rules! or {
    ( $( $p:tt ),* ) => {{
        || format!($($p),*)
    }};
}

fn run() -> Result<()> {
    use std::fs::File;
    // This operation will fail
    let p = "foo";

    File::open(p).chain_err(or!("unable to open {} file", p))?;

    Ok(())
}

Read as: invoke chain_error method with a variadic arg list.

Related