Stream: project-error-handling

Topic: `{:!}` fmt specifier RFC


view this post on Zulip Jane Lusby (Feb 27 2021 at 01:04):

So I think this idea is getting near the point where we should write an RFC. I had a call with @Mara today where we talked a lot about the reporting ideas I was going to blog about and she largely convinced me that much of it possibly doesn't belong in std

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:04):

im still re-evaluating

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:05):

but there seems to be a minimal feature subset of the idea that we both agreed on completely, that I think could easily support the reporting hooks if it ends up making sense to include those in std

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:05):

Heres the basic idea

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:06):

add a {:!} specifier to the fmt grammar, this expects arguments to implement the Error trait, or possibly just deref / coerce to &dyn Error + 'static

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:06):

when you print an error with this specifier it will actively utilize the Error trait interface to print the error, iterating over sources

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:07):

the default format will be to do error message: source error message: source source error message: ...

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:07):

then {:#!} could do the multi line format similar to eyre / anyhow and print the backtrace if one was captured

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:08):

then we would specialize the Termination impl on Result to use this format specifier instead of {:?} when the error type impls Error

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:09):

additionally, we may want to specialize unwrap and expect to use this fmt specifier

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:09):

One unanswered question:

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:10):

I'd like to have the {:!} have special meaning when used in a panic! macro, where it includes the formatted string as the Payload, but also has the argument passed into {:!} preserved as the source error

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:10):

assuming we've moved the error trait to core and added integration to PanicInfo

view this post on Zulip nagisa (Feb 27 2021 at 01:11):

Jane Lusby said:

add a {:!} specifier to the fmt grammar, this expects arguments to implement the Error trait, or possibly just deref / coerce to &dyn Error + 'static

That seems inconsistent with other kinds of formatting specifiers, why tie this to the Error trait rather than adding fmt::Bang or similar?

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:13):

nagisa said:

Jane Lusby said:

add a {:!} specifier to the fmt grammar, this expects arguments to implement the Error trait, or possibly just deref / coerce to &dyn Error + 'static

That seems inconsistent with other kinds of formatting kinds, why tie this to the Error trait rather than adding fmt::Bang or similar?

because the Error trait is about providing structured access to the context that needs to be reported, the goal here is to provide a Reporter built into our fmt machinery that consumes the error trait and lays out a simple report

view this post on Zulip nagisa (Feb 27 2021 at 01:13):

You mentioned the default format, but I don't see how one would override it with something else in absence of such a trait.

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:13):

nagisa said:

You mentioned the default format, but I don't see how one would override it with something else in absence of such a trait.

via Formatter flags

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:13):

the same way you can have an alt Debug format and a regular one with the same impl

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:14):

you go if f.alt() { // alternate formatting logic .. } else { // default formatting logic }

view this post on Zulip nagisa (Feb 27 2021 at 01:14):

Right, but you can't customize how your type gets printed out via formatter flags. AFAICT you'd get A or B, where A and B are decided by libstd.

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:14):

yea

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:14):

this is just providing a minimal not customizable set of formats for errors

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:15):

like if there was a fmt::Bang trait

view this post on Zulip nagisa (Feb 27 2021 at 01:16):

I'm missing context to discuss this, I guess.

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:16):

this would be like there was also a single type that implemented it, and it impls fn new(error: &dyn Error + 'static) -> Bang<'_> and every time you use a fmt macro it always wraps your error in the Bang and uses its Bang impl to print it

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:16):

nagisa said:

I'm missing context to discuss this, I guess.

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:17):

so the context is, if you print an error using Display or Debug you usually lose context

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:17):

because it wont iterate through the sources for you

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:17):

so we want to make it easy and encouraged to print sources

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:18):

right now the common way to do that is have a type that just wraps the dyn error and implements debug, then inside its debug impl iterates over sources and prints the report the way you want

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:19):

{:!} acts as if it was implicitly wrapping the error in a reporting type like this

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:19):

and implies "the format machinery will iterate over your sources and print this for you, using its pre-defined formats, with some customizability via format flags"

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:20):

this wouldn't be the full story on error reporting, but the idea is that more complex runtime error reporting is usually specific to the kind of application you're writing

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:20):

and hard to abstract over

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:21):

so we want to leave that to libraries for now

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:21):

so eyre would still exist, and would let you customize your report formats and would just use Debug

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:21):

or actually it probably wouldn't but thats due to changes enabled by the new Try rfc

view this post on Zulip nagisa (Feb 27 2021 at 01:33):

My two primary concerns here are: As a developer I typically want to print my errors as

error: <display>
  caused by: <display>
  caused by: ...

rather than a single line (or anything else) so to me being able to change how things end up looking is important. With that in mind I would end up being unable to utilize {:!} as proposed, much like I cannot utilize the termination trait (for the same reasons).

On the rustc side of things, I find it somewhat weird to tie formatting machinery to an almost unrelated concept of error handling. On the other hand, I can see why you'd want the formatting machinery to be responsible for implementing the walking up the causation chain – if its left up to the user many will simply forget!

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:37):

@nagisa {:#!} would look like the format you just mentioned

view this post on Zulip nagisa (Feb 27 2021 at 01:37):

I wonder if specialization could be a solution to the problem of people wanting to specify a custom format/print.

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:38):

nagisa said:

I wonder if specialization could be a solution to the problem of people wanting to specify a custom format/print.

if we want to customize it then I think we'd use the same solution eyre does

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:38):

which is to have a trait that is called to do the iteration and printing

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:38):

fn report(&self, error: &(dyn Error + 'static), f: &mut fmt::Formatter<'_>) -> fmt::Result

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:39):

then {:!} would just call this, and users could install their own reporter with a function like set_hook for panics

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:39):

that was the original proposal

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:40):

but @Mara raised the point that the way you want to format it is often dependent upon the system you're using to report the error, and not necessarily universal across the application

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:41):

so the default fmt machinery and panic might want to use some simple default formats, but tracing would probably have its own way of recording errors as structured data

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:42):

and if you need to customize the output, then the right solution is probably to wrap it in another type that implements Display

view this post on Zulip Jane Lusby (Feb 27 2021 at 01:42):

and that type can have the customizing hooks if you want like eyre

view this post on Zulip scottmcm (Feb 27 2021 at 03:25):

Jane Lusby said:

and if you need to customize the output, then the right solution is probably to wrap it in another type that implements Display

I've also sometimes seen this done as fn something(&self) -> impl Display, or whatever, instead of requiring passing the formatter to something. Dunno if that fits what you're doing...

view this post on Zulip Jane Lusby (Feb 27 2021 at 03:26):

yea

view this post on Zulip Jane Lusby (Feb 27 2021 at 03:26):

thats pretty much how I'd recommend doing it in practice

view this post on Zulip Jane Lusby (Feb 27 2021 at 03:26):

that impl Display would probably be impl Display + '_ and store the &self ref inside it

view this post on Zulip Lokathor (Feb 27 2021 at 04:08):

If we do this, we should also make a fmt::Bang trait in general, and then errors simply always implement that trait

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:08):

oooo

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:08):

yes

view this post on Zulip Lokathor (Feb 27 2021 at 04:09):

because then we can make some sort of convention such as "walk this" for bang

view this post on Zulip Lokathor (Feb 27 2021 at 04:09):

eg, iterators can now bang fmt if their items can fmt, and it just prints out the iterator steps

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:13):

I'd want to call it std::fmt::Report

view this post on Zulip Lokathor (Feb 27 2021 at 04:13):

however there's also already a bit of awkwardness between debug / display / the other formatters, so maybe we could sit down and fix that up, and then use a design that is compatible with the current edition and also with a potential break for a future edition (if the fix up plan requires a break)

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:13):

rather than Bang

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:13):

oh

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:13):

wait you mean fmt refactor?

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:13):

or do you mean how they interact with the error trait

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:14):

because in the other topic today I talked a bunch about how I think the new Try trait lets us impl Error on AnyError style types

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:14):

and this has massive implications, including on how well the fmt impls for errors compose

view this post on Zulip Lokathor (Feb 27 2021 at 04:15):

so my problem with them is that all the non-{debug | display} traits are supposed to be the Display form of whatever they do, and then they secretly also combine with the ? to get a Debug form of that thing, but end users can't easily get this, so you don't benefit from that unless you've derived

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:15):

i dont follow

view this post on Zulip Lokathor (Feb 27 2021 at 04:18):

so, like, {:x} for LowerHex, but you can also write {:x?} and that's Debug (LowerHexMode), the ? "wins". Except users writing their own Debug impls can't see that the format was an x and print the debug numbers in lower hex, so if you didn't derive the impl then you don't get this special behavior

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:18):

{:!?} wouldn't be valid

view this post on Zulip Lokathor (Feb 27 2021 at 04:19):

but what about {:x!}

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:19):

ignored

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:19):

or maybe just not accepted in the grammar

view this post on Zulip Lokathor (Feb 27 2021 at 04:19):

mmmmmaybethat'sfineiguess

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:19):

lol

view this post on Zulip Lokathor (Feb 27 2021 at 04:20):

i still think we would be best served if we took a little time to make sure that formatting overall, as a system, was staying cohesive, and that we were leaving ourselves the most open space for the future

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:21):

fair

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:21):

I can draft an initial RFC that doesn't focus on the fmt integration

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:21):

but on providing a reporter to drive the error trait interface in the basic case

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:21):

or

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:21):

iunno

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:22):

is someone actively working on the fmt overhaul?

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:22):

we could do a much more minimal change

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:23):

and add a report() method that is blanket impled for errors

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:23):

on some new trait

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:23):

that just creates a Display type that does the formatting described

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:24):

so println!("Error: {}", error.report());

view this post on Zulip Lokathor (Feb 27 2021 at 04:26):

It's a pet peeve of mine, but no one is working on it, because before today no one wanted to see any particular new stuff so i didn't feel pressed for time or anything.

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:26):

this could just be a method couldnt it?

view this post on Zulip Lokathor (Feb 27 2021 at 04:27):

yeah it can just be a method that returns an impl Display

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:27):

if we require : Sized

view this post on Zulip Lokathor (Feb 27 2021 at 04:27):

it could return... &dyn Display?

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:27):

dont think so,

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:27):

that would prevent temporaries

view this post on Zulip Lokathor (Feb 27 2021 at 04:28):

temporary whats?

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:28):

wrappers that provide the Display impl

view this post on Zulip Lokathor (Feb 27 2021 at 04:28):

hm

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:29):

i think making it : Sized is fine and possibly good

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:29):

because we can still do a default impl that will work on dyn Errors

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:29):

that returns the default std::error::Report type

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:29):

that does the simple reporting via its Display impl

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:35):

I think i prefer the fmt based solution though

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:35):

because it more naturally leads to integration with the panic runtime

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:36):

tho eeh

view this post on Zulip Jane Lusby (Feb 27 2021 at 04:36):

honestly that feels half baked

view this post on Zulip nagisa (Feb 27 2021 at 12:27):

Making ! a formatting flag rather than a separate specifier could be an option, which much like fmt::Report it would force users to carefully not miss implementing the walking of causation chain by themselves…

The # flag could be repurposed too, e.g. to say that conventionally display impls with # should also format in their cause-chain somehow (that way not requiring any changes other than to how Display impls are written)

view this post on Zulip Josh Triplett (Mar 01 2021 at 19:35):

I just saw this topic this morning. I like the general idea of having a standard "format this as an error" (and I like the suggestion of making it a trait, as well).

view this post on Zulip Josh Triplett (Mar 01 2021 at 19:38):

I also wonder if min_specialization would be enough to be able to have specialized implementations for types that also implement Error.


Last updated: Jan 26 2022 at 14:02 UTC