rust-web / twig

Twig templating engine for Rust; work in progress.
http://rust-web.github.io/twig
Other
8 stars 1 forks source link

error handling #1

Open colin-kiegel opened 8 years ago

colin-kiegel commented 8 years ago

It would be great, if we find a common way here. That will make everything else a lot easier!

Ok, here is our starting point :-)

twig-rust

Definitions are in: src/error/* - but I am sure, it has room for improvement. It definitely needs some cleanup, due to boiler-plate when defining new error definitions.

twig-rs

I am keeping all errors in enums/structs and then converting them to string on actual display. There are 3 groups: template for lexer and parser errors, engine for loading/caching, and runtime which is a bit in flux. On top of that, there is master twig Error which can contain any of these children, and in case of engine/runtime, the chain of error causes. Of course, all chilren are implementing Into trait to be converted to "master" error.

I have found one idea useful: separating location from actual error. There is this At struct which wraps actual error inside. To convert to it, there is method on error named at, which requires location in file. So I do this:

TemplateError::ExpectedTokenTypeButReceived(
    (expected.into(), Received::EndOfStream)
).at(line)

Note that I only have line numbers, but I was planning to expand this to line/col later.

colin-kiegel commented 8 years ago

Reopening (see https://github.com/rust-web/twig/pull/12#issuecomment-162331981 + following 2 comments).

Look at alternatives to provide

/// Runtime error with stack trace.
#[derive(Clone, Debug)]
pub struct Traced<T: Clone, Debug> {
    pub error: T,
    /// Trace entries which may have location and/or another error. Maybe another struct.
    pub trace: Vec<(Location, Option<T>)>,
}
colin-kiegel commented 8 years ago

I did it quite similar to your suggestion - but instead of Vec<(Location, Option<T>)> the trace only is Vec<Location>.

We can use the inner error for rust-style error-composition MyError::IO(std::io::Error). Of course we loose information about when exactly such nesting of errors occurs along the backtrace, but I think this is totally acceptable. Presentation will be a lot easier, because we can separate inner error and trace presentation. The first thing the user will see is the inner error.

What do you think?

Nercury commented 8 years ago

I am willing to try it :)

colin-kiegel commented 8 years ago

Ok. :-)

Btw: I was not sure about one detail - should Traced<T: Error> (a) deref to T: Error (current choice) (b) implement Error by delegating to T: Error (c) neither one (d) don't care

I think Deref is usually reserved for smart pointers and Newtype-Tuple-Structs like struct Inches(i32);. On the other hand implementing Error would allow Traced<Traced<T: Error>> - which is not necessarily something bad, but it could happen by accident.

Nercury commented 8 years ago

I would do no magic until really needed. So I think (c) until proven otherwise :)

colin-kiegel commented 8 years ago

Ok, I will remove (a).

I had another thought about it. I think what we want is composable errors and giving the caller the flexibility to convert our traced errors into opaque trait objects. If a consumer of Twig wants to convert our Traced<TwigError> into Box<Error> with the following code, he will fail:

fn foo() -> Result<(), Box<Error>> {
    let _ = try!(twig::Engine::fail());
    // ...
}

The try macro will try to call <Box<Error> as From>::<Traced<TwigError>>::from()

So actually I think we should do (b).

Nercury commented 8 years ago

Ok.