In librsvg we are -><- this close to being able to publish the library as a crate on crates.io. One of the last tasks is to make the public error types follow Rust best practices, and to allow for some changes in the future without API breaks. Let me explain.
Librsvg had two public error types more or less like this:
// Errors which can only happen during the loading stage
pub enum LoadingError {
// we use libxml2, and don't want to expose its error type publically, hence a String
XmlParseError(String),
// yeah yeah if you can't allocate how can you even String
OutOfMemory(String),
// public API takes `url: &str` and this is what you get if it's malformed
BadUrl,
// for when the public API lets the caller pass some CSS chunk
BadCss,
// Toplevel element is not a `<svg>`
NoSvgRoot,
// Implementation detail, see below
Io(glib::Error),
// An implementation limit (mitigation for malicious files) was hit
LimitExceeded(ImplementationLimit),
// Catch-all for other errors during loading
Other(String),
}
// Errors which can only happen during the rendering stage
pub enum RenderingError {
// Something inside Cairo exploded
Cairo(cairo::Status),
// Similar, mitigation for malicious SVGs
LimitExceeded(ImplementationLimit),
// when the API lets you pass an element ID to render
IdNotFound,
InvalidId,
OutOfMemory(String),
}
I don't think the public error types should expose implementation details like "librsvg uses libxml2 internally" or "the Cairo rendering backend had a problem". In particular, librsvg is going to change to Rust-only code for those tasks at some point in the future, and I would like to prevent an API break due to changing the error types. Should we move to Box<dyn Error + ...>
for those variants?
For historical/practical reasons, librsvg uses Glib's GIO for reading files and such. The Rust API can also take just an AsRef<Path>
and just use GIO internally without you ever knowing, so exposing a glib::Error
(the Rust binding for GError
from C) doesn't seem right. Would it be appropriate to type-erase this with Box as well?
It seems we do need a catch-all case for obscure corners in the code that cause things to fail. I wonder how other libraries do this.
As much as I would like to have actual types for each error variant, I think this plays badly with the following:
- Changes in the internals in the future - we'll switch to Rust code for things like XML and rendering; in principle GIO could be hidden behind a feature flag.
- For librsvg it is important to have localized error messages (it doesn't, right now, but that got lost in the port to Rust), so e.g. an image viewer can tell you "could not load SVG because of $blahblah" in your language. I haven't seen discussion of
impl Display for MyError
returning localized messages, but for user-visible things this sounds about right.
I think there's a vibe of "use concrete types for library errors, and type-erased ones for application errors" but librsvg seems to sit kind of in the middle?