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 description
says:
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?
--David