Hello. I think that generating good error messages in user-facing programs is very important. I am frustrated when I see messages like "Connection failed." or "Failed to save file.". Just to be clear, I am talking about the error messages generated at runtime by a program written in Rust; I am not talking about errors from the Rust compiler itself.
I have written several GUI applications in C# and C++ for accessing USB devices that generate error messages that look like this:
Failed to connect to the device. Unable to get the firmware version from the device. USB control transfer failed. Error code 0x1f.
Each sentence in the error message is generated by a different level of the call stack: the first sentence comes from a high-level part of the GUI that is just trying to connect to a USB device. The second sentence would probably come from a cross-platform library for accessing the device. The third sentence would come from the USB abstraction layer (which allows us to write cross-platform code for accessing USB devices). The error code in the fourth sentence would come from the operating system itself (e.g. from
GetLastError() in Windows or
errno in Linux).
In my opinion, the error message above is really useful to users and developers. The typical user can stop reading after one or two sentences and have a pretty good idea of what went wrong. The application's developer can read the whole thing and it's almost as good as having a stack trace.
My question: What is the right way to generate such an error message in Rust?
In C#, this is how I did it: the .NET Exception class has a Message property that returns one or more complete sentences describing the error and an InnerException property. As an exception propagates up the call stack, any function can catch it, wrap it inside a new exception using the
InnerException property, and then throw the new exception. When the exception is caught by high-level GUI code, we have a linked list of exceptions tied together by the
InnerException property. We can iterate through this list, check the types of the exceptions, and also concatenate their
Message properties together to get a nice error message.
The Rust Error type has almost all the same machinery as .NET. The
description method returns a string, and the
cause method returns the low-level inner error. However, the big problem is that the documentation of
The description should not contain newlines or sentence-ending punctuation, to facilitate embedding in larger user-facing strings.
I don't understand this design. Why does the lack of sentence-ending punctuation help when generating a user-facing error string? I just don't see how that would work; is there any Rust code in use today that actually concatenates the
description of multiple errors together to make a user-facing string? How should I implement
description if my error takes two or more sentences to describe? The official Microsoft error codes each are associated with an English sentence with a period at the end. If we want to embed those sentences in a Rust error, are we really supposed to strip off the period, and why would that be a good idea?