rfcs icon indicating copy to clipboard operation
rfcs copied to clipboard

Add an error return method to `format`

Open cheeroncode opened this issue 4 years ago • 14 comments

Add an error return method to format! The format! macro never fails and the compiler reports an error when it does.

let a:String = format!("{}",9);

Sometimes, however, I may need to reorganize the formatted string in a custom program macro based on user input. When the user typed something wrong, I wanted to catch the compiler error and re-report the error in the right place. I wish there was a way to return the wrong or a way to do this, thanks a lot.

let b:Result<String,String> = try_format!("{:?}",9);
match b {
    Ok(ok) => {
        // do something...
    },
    Err(err) => {
        // Custom error handling...
    },
} 

Here's a scenario that could go wrong to make my intentions more obvious.

I provide my users with a macro called comment!, which takes a format argument like format! :

#[comment("{:1$}","hi~",10)]
fn do(){
    //....
}

comment! inserts a new line of code into method do :

fn do(){
    println!("{:1$}","hi~",10);
    //....
}

If the user accidentally enters formatting parameters incorrectly:

#[comment("{:$}","hi~",10)]
           ^^^^
fn do(){
    //....
}

Then, the newly generated code will be wrong. The compiler will give a vague error on #[comment(...)] and cannot indicate the correct location:

fn do(){
    println!("{:$}","hi~",10); // ^^^^ section is what actually went wrong, missing the width index.
              ^^^^             
    //....
}          

Now, I want to check that the formatting parameters are correct when I generate 'println'. I think the best way is to catch any errors the compiler gives you, And display the error in the corresponding position of comment! :

#[comment("{:$}","hi~",10)]
           ~~~~
fn do(){
    //....
}

So, I wish there was a try_format!. When the parameter is not correct, can return an error, let me control.

cheeroncode avatar Dec 12 '21 09:12 cheeroncode

This sounds like something that could be added to calm_io before being added to the standard library.

shepmaster avatar Dec 12 '21 12:12 shepmaster

@shepmaster Calm_io can accomplish its goals with functionality already provided in the library. I'd love to see a way to do that and add try_format! to it. But for now, there is no way to do something like try_format! from the standard library, and everything associated with it is private.

cheeroncode avatar Dec 12 '21 14:12 cheeroncode

Actually, it's not clear what you mean by failing. Formatting to a string cannot fail at all beyond memory allocation. You also say "catch the compiler error", but then you show runtime code like match. Your example also doesn't fail. It sounds like you need to make a clearer example of the problem and proposed solution(s).

shepmaster avatar Dec 12 '21 15:12 shepmaster

@shepmaster You're right, I'm just stating my intent, there's nothing wrong with the sample code. I've added some scenario code to make it more explicit.

cheeroncode avatar Dec 12 '21 15:12 cheeroncode

☹️☹️

cheeroncode avatar Dec 13 '21 17:12 cheeroncode

Formatting to a string cannot fail at all beyond memory allocation.

technically it can panic if the fmt impl returned an error

use std::fmt;

struct Y;

impl fmt::Debug for Y {
    fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
        Err(fmt::Error)
    }
}

fn main() {
    dbg!(format!("{:?}", Y)); 
    // panic: "a formatting trait implementation returned an error: Error"
}

but for built-in types like str and integers they won't fail.

kennytm avatar Dec 15 '21 18:12 kennytm

@cheeroncode I'm afraid your suggestion doesn't really make sense.

For your specific example, as long as your #[comment] attribute macro generates code with the correct span information, then the compiler should be able to report the error in the correct place.

The reason your suggestion of try_format! doesn't make sense is because the kinds of errors you are trying to catch from format!, are generated at compile-time. The logic for parsing the curly brackets from the format string doesn't even exist at runtime. Furthermore, this kind of error should not be reported at runtime, because it's known at compile-time whether the format string is valid or not.

If you are unable to generate the correct spans, then you should validate the format string yourself within your procedural macro, and report the error directly using compile_error!.

Diggsey avatar Dec 15 '21 19:12 Diggsey

Formatting to a string cannot fail at all beyond memory allocation.

technically it can panic if the fmt impl returned an error

use std::fmt;

struct Y;

impl fmt::Debug for Y {
    fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
        Err(fmt::Error)
    }
}

fn main() {
    dbg!(format!("{:?}", Y)); 
    // panic: "a formatting trait implementation returned an error: Error"
}

but for built-in types like str and integers they won't fail.

Thanks, but what I really want is an error message returned by the compiler when formatting placeholders fail.

cheeroncode avatar Dec 16 '21 08:12 cheeroncode

@cheeroncode I'm afraid your suggestion doesn't really make sense.

For your specific example, as long as your #[comment] attribute macro generates code with the correct span information, then the compiler should be able to report the error in the correct place.

The reason your suggestion of try_format! doesn't make sense is because the kinds of errors you are trying to catch from format!, are generated at compile-time. The logic for parsing the curly brackets from the format string doesn't even exist at runtime. Furthermore, this kind of error should not be reported at runtime, because it's known at compile-time whether the format string is valid or not.

If you are unable to generate the correct spans, then you should validate the format string yourself within your procedural macro, and report the error directly using compile_error!.

Maybe you don't quite understand what I need, my comment! actually runs at compile time and does not generate run-time code. I can get the correct span in comment! But the println! code generated by reorganizing the contents of the span can be corrupted by user input errors. I want to explicitly indicate where this part of the error occurred, but I have to validate all input parameter placeholders. These formats! already provide, but there is no public API. That's why I want try_format!.

cheeroncode avatar Dec 16 '21 09:12 cheeroncode

sounds like what you really want is a proc_macro API compiling the format string, similar to #3200.

kennytm avatar Dec 17 '21 08:12 kennytm

As far as I can see the 'const_format' crate has a macro that produces a const function that can return an error at compile-time if the arguments do not match. But it requires the 'const_mut_refs' feature which is only available on unstable

https://docs.rs/const_format/latest/const_format/macro.try_.html

nielsle avatar Dec 17 '21 09:12 nielsle

sounds like what you really want is a proc_macro API compiling the format string, similar to #3200.

It's similar in terms of ease of use. It is easier to complete my requirements by calling cargo check and then handling the returned errors. Currently, when a user enters incorrect data, I manually encode to validate the placeholder; Repeat the part of the format! validation placeholder. Then a simple error message at compile time; In proc macro, if there is such an API, it checks the regenerated code and returns any errors found. That would be perfect.

cheeroncode avatar Dec 17 '21 10:12 cheeroncode

As far as I can see the 'const_format' crate has a macro that produces a const function that can return an error at compile-time if the arguments do not match. But it requires the 'const_mut_refs' feature which is only available on unstable

https://docs.rs/const_format/latest/const_format/macro.try_.html

@nielsle Thank you, they don't apply to me, my requirement is that I get the compiler's error in the Proc macro and decide how to deal with it, rather than having the compiler report it directly.

cheeroncode avatar Dec 17 '21 10:12 cheeroncode

This may be a good idea :>

peepo5 avatar Apr 20 '22 09:04 peepo5