Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

rust

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.

like image 374
Vittorio Romeo Avatar asked Jul 05 '17 09:07

Vittorio Romeo


People also ask

How do you implement a string format in Python?

The format() method formats the specified value(s) and insert them inside the string's placeholder. The placeholder is defined using curly brackets: {}. Read more about the placeholders in the Placeholder section below. The format() method returns the formatted string.

What is format method in string?

In java, String format() method returns a formatted string using the given locale, specified format string, and arguments. We can concatenate the strings using this method and at the same time, we can format the output concatenated string.

What is string format () used for Java?

The Java String. format() method returns the formatted string by a given locale, format, and argument. If the locale is not specified in the String. format() method, it uses the default locale by calling the Locale.


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.

like image 112
Shepmaster Avatar answered Oct 12 '22 05:10

Shepmaster


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.

like image 26
attdona Avatar answered Oct 12 '22 07:10

attdona