<div dir="ltr">Hi Mehdi,<div><br>> > I think (though I haven't tested this) that most lambdas should inline away to next to nothing, so I don't expect the overhead to be noticeable in either case.</div><div><div><br></div><div>> My guess is that is will be very dependent on what the lambdas is actually capturing and how many there are, and if <span style="font-family:monospace,monospace">catchTypedErrors </span>is actually inlined. </div><div>> But I’m sure we would sort this out anyway *if* it turns out they’re not ;)</div><div><br></div><div class="gmail_extra">Yep. Some experimentation is warranted here.</div><div class="gmail_extra"><br></div><div class="gmail_extra">- Lang.</div><div class="gmail_extra"><br><div class="gmail_quote">On Wed, Feb 3, 2016 at 1:30 PM, Mehdi Amini <span dir="ltr"><<a href="mailto:mehdi.amini@apple.com" target="_blank">mehdi.amini@apple.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex"><div style="word-wrap:break-word"><br><div><blockquote type="cite"><span class=""><div>On Feb 3, 2016, at 11:32 AM, Lang Hames <<a href="mailto:lhames@gmail.com" target="_blank">lhames@gmail.com</a>> wrote:</div><br></span><div><div dir="ltr">Hi Mehdi,<div><br></div><span class=""><div>> <span style="font-size:13px">Side question on the related work because I’m curious: did you look for similar generic scheme for error handling offered by other C++ libraries? Maybe the common scheme is to use C++ exceptions but since many folks disable them (hello Google ;-) ) there has been probably many other attempts to address this.</span></div><div><span style="font-size:13px"><br></span></div><div>I did look around, but not very hard. Among the people I asked, everyone was either using exceptions, std::error_code, or had turned on the diagnostic that James Knight suggested. I did some preliminary web-searching, but that didn't turn up anything interesting. If anybody has seen other error handling schemes of note I'd love to hear about them.</div><div><span style="font-size:13px"><br></span></div><div><span style="font-size:13px">> </span><span style="font-size:13px">On this topic of not paying the price on the non-error code path, it would be nice to not pay for constructing all the lambda captures when there is no error.</span></div><div><span style="font-size:13px"><br></span></div><div>If your catchTypedErrors call is under an if-test then the lambdas are in a scope that won't be entered on the non-error path:</div><div><br></div><div><font face="monospace, monospace">if (auto Err = foo())</font></div><div><font face="monospace, monospace">  if (auto E2 = catchTypedErrors(std::move(Errs), <lambdas here>))</font></div><div><font face="monospace, monospace">    return;</font></div></span></div></div></blockquote><div><br></div><div>Sure, but I was looking at avoiding to have to do this extra level of manual test, to reduce the “boilerplate” effect. For instance I’d like to write something like (straw man):</div><div><br></div><div><br></div><div>TRY(foo())</div><div>CATCH(<span style="font-family:monospace,monospace">lambda1 here)</span></div><div>CATCH(<span style="font-family:monospace,monospace">lambda2 here)</span></div><div><div>CATCH(<span style="font-family:monospace,monospace">lambda3 here)</span></div><div><span style="font-family:monospace,monospace"><br></span></div><div><span style="font-family:monospace,monospace">or (straw man as well):</span></div><div><span style="font-family:monospace,monospace"><br></span></div><div><span style="font-family:monospace,monospace">auto Err = foo();</span></div><div><span style="font-family:monospace,monospace">HANDLE(Err) {</span></div><div><span style="font-family:monospace,monospace">  MATCH(MyCustomType) {</span></div><div><span style="font-family:monospace,monospace">    // code</span></div><div><span style="font-family:monospace,monospace">  } </span></div><div><span style="font-family:monospace,monospace">  MATCH(MyOtherCustomType) {</span></div><div><span style="font-family:monospace,monospace">    // code</span></div><div><span style="font-family:monospace,monospace">  }</span></div><div><span style="font-family:monospace,monospace">  DEFAULT {</span></div><div><span style="font-family:monospace,monospace">  </span></div><div><span style="font-family:monospace,monospace">  }</span></div><div><span style="font-family:monospace,monospace">}</span></div><div><span style="font-family:monospace,monospace"><br></span></div><div><span style="font-family:monospace,monospace"><br></span></div></div><span class=""><div><br></div><blockquote type="cite"><div><div dir="ltr"><div><span style="font-size:13px"><br></span></div><div>I think (though I haven't tested this) that most lambdas should inline away to next to nothing, so I don't expect the overhead to be noticeable in either case.</div></div></div></blockquote><div><br></div></span><div>My guess is that is will be very dependent on what the lambdas is actually capturing and how many there are, and if <span style="font-family:monospace,monospace">catchTypedErrors </span>is actually inlined. </div><div>But I’m sure we would sort this out anyway *if* it turns out they’re not ;)</div><div><br></div><div>— </div><span class=""><font color="#888888"><div>Mehdi</div></font></span><div><div class="h5"><blockquote type="cite"><div><div dir="ltr"><div><span style="font-size:13px"><br></span></div></div><div class="gmail_extra"><br><div class="gmail_quote">On Wed, Feb 3, 2016 at 10:55 AM, Mehdi Amini <span dir="ltr"><<a href="mailto:mehdi.amini@apple.com" target="_blank">mehdi.amini@apple.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex"><div style="word-wrap:break-word"><br><div><blockquote type="cite"><span><div>On Feb 3, 2016, at 10:48 AM, Lang Hames <<a href="mailto:lhames@gmail.com" target="_blank">lhames@gmail.com</a>> wrote:</div><br></span><div><div dir="ltr">Hi Mehdi,<div><br></div><span><div>> <span style="font-size:13px">For a generic error class it is not an option indeed, but I was looking at it in the context of LLVM internal use, so just like our RTTI is not an option for “generic RTTI” but fits our need, we could (not should) do the same with ErrorHandling.</span></div><div><span style="font-size:13px"><br></span></div><div>Definitely. If this was LLVM only there'd be a strong case for using the existing RTTI system. The reason for the new RTTI system is that it would let us re-use this error class in other LLVM projects (LLD, LLDB, etc) without having to enumerate their errors in LLVM. </div></span></div></div></blockquote><div><br></div><div>I think it is great :)</div><div><br></div><div>Side question on the related work because I’m curious: did you look for similar generic scheme for error handling offered by other C++ libraries? Maybe the common scheme is to use C++ exceptions but since many folks disable them (hello Google ;-) ) there has been probably many other attempts to address this.</div><span><br><blockquote type="cite"><div><div dir="ltr"><div><br></div><div>> <span style="font-size:13px">Nice, and since this is on the error path we don’t care if it is not “as fast as” the custom LLVM RTTI.</span></div><div><span style="font-size:13px"><br></span></div><div><span style="font-size:13px">Yep.</span></div></div></div></blockquote><div><br></div></span><div>On this topic of not paying the price on the non-error code path, it would be nice to not pay for constructing all the lambda captures when there is no error. I can imagine many way of expressing a “try/catch” like syntax to achieve this using macros, but not really without that. </div><div>Have you thought about this?</div><div><br></div><div>Thanks,</div><div><br></div><div>Mehdi</div><div><div><div><br></div><br><blockquote type="cite"><div><div dir="ltr"><div><span style="font-size:13px"><br></span></div><div><span style="font-size:13px">> </span><span style="font-size:13px">OK got it now, the “empty” </span><span style="font-size:13px;font-family:monospace,monospace">TypedError()</span><span style="font-size:13px">is the key :)</span></div><div style="font-size:13px">> (and I was using success/failure terminology reversed compare to you)</div><div><br></div><div>Yeah - this is confusing. It's no worse than 'std::error_code()', but it's no better either. Maybe introducing a utility wrapper like 'TypedErrorSuccess()' or even 'ESuccess()' would make things more readable.</div><div><br></div><div>- Lang.</div><div><br></div></div><div class="gmail_extra"><br><div class="gmail_quote">On Wed, Feb 3, 2016 at 8:14 AM, Mehdi Amini <span dir="ltr"><<a href="mailto:mehdi.amini@apple.com" target="_blank">mehdi.amini@apple.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex"><div style="word-wrap:break-word"><br><div><blockquote type="cite"><span><div>On Feb 2, 2016, at 11:33 PM, Lang Hames <<a href="mailto:lhames@gmail.com" target="_blank">lhames@gmail.com</a>> wrote:</div><br></span><div><div dir="ltr">Hi Mehdi,<div><br></div><span><div>> If you subclass a diagnostic right now, isn’t the RTTI information available to the handler, which can then achieve the same dispatching / custom handling per type of diagnostic?<br></div><div><div>> (I’m not advocating the diagnostic system, which I found less convenient to use than what you are proposing)</div><div><br></div><div>I have to confess I haven't looked at the diagnostic classes closely. I'll take a look and get back to you on this one. :)</div><div><br></div><div>> > For that you need RTTI, so this patch introduces a new RTTI scheme that I think is more suitable for errors types*, since unlike LLVM's existing RTTI system it doesn't require you to enumerate the types up-front.</div><div>> It looks like I’m missing a piece of something as it is not clear why is this strictly needed. I may have to actually look closer at the code itself.</div><div class="gmail_extra"><br></div><div class="gmail_extra">LLVM's RTTI requires you to define an enum value for each type in your hierarchy (see <a href="http://llvm.org/docs/HowToSetUpLLVMStyleRTTI.html" target="_blank">http://llvm.org/docs/HowToSetUpLLVMStyleRTTI.html</a>), which means you need to know about all your potential subclasses up-front. That's not an option for a generic error class that might be subclassed in arbitrary ways.</div></div></span></div></div></blockquote><div><br></div><div>For a generic error class it is not an option indeed, but I was looking at it in the context of LLVM internal use, so just like our RTTI is not an option for “generic RTTI” but fits our need, we could (not should) do the same with ErrorHandling.</div><span><div><br></div><br><blockquote type="cite"><div><div dir="ltr"><div><div class="gmail_extra"><br></div><div class="gmail_extra">The new RTTI system uses something closer to LLVM's Pass IDs: TypedErrorInfo (a utility which all errors must inherit from) introduces a new static char for each error type and uses its address as an ID. When you ask an error value "are you a subclass of type T" (via the isSameOrSubClass method) the call goes to the parent TypedErrorInfo object, which compares T's ID with its own. If it matches it returns true, if it doesn't match then the call gets forwarded to the parent class, then its parent class, and so on. If you hit the root of the type-tree (i.e. TypedErrorInfoBase) without matching the ID, then you weren't a subclass of T.</div></div></div></div></blockquote><div><br></div></span><div>Nice, and since this is on the error path we don’t care if it is not “as fast as” the custom LLVM RTTI.</div><span><br><blockquote type="cite"><div><div dir="ltr"><div><div class="gmail_extra"><br></div><div class="gmail_extra">> <span style="font-size:13px">Sure, this case shows “success” of the handler, now what is a failure of the handler and how is it handled?</span></div><div class="gmail_extra"><span style="font-size:13px"><br></span></div><div class="gmail_extra">Sorry - that was a bad example to choose: That was actually showcasing failure, not success. Success looks like this:</div><br><font face="monospace, monospace">TypedError bar() {<br>  TypedError Err = foo;<br>  if (auto E2 =<br>    catchTypedErrors(std::move(Err),<br>      handleTypedError<MyError>([&](std::unique_ptr<MyError> M) {</font></div><div><font face="monospace, monospace">        // Deal with 'M' somehow.<br>        return TypedError();<br>      }))<br>    return E2;</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">  // Proceed with 'bar' if 'Err' is handled.<br>}</font><div class="gmail_extra"><br></div><div class="gmail_extra">A key observation is that catchTypedErrors itself returns an error. It has to, because you may not have provided it with an exhaustive list of handlers, and it needs a way to return unhanded errors. So: If no handler gets invoked, catchTypedErrors will just return 'Err' again. If 'Err' was an error, then E2 will also be an error, and you'll immediately return from 'bar', passing responsibility for the error up the stack. So far so good. Now consider what we should do if, instead, we *did* invoke a handler. One option would be to say that if a handler gets invoked then catchTypedErrors always returns 'TypedError()', indicating success, but that's an assertion that any error that's caught is definitely resolved. Sadly, we can't rely on that, so instead we allow the handler to supply the return value for catchTypedErrors. If the handler supplies 'TypedError()' then the error is considered truly 'handled' - E2 becomes TypedError(), the if condition is false (indicating there is no error) and the function goes on its way. If the handler supplies an error, then E2 becomes an error, the if condition is true, and we exit the function immediately, returning the error to the caller.</div><div class="gmail_extra"><br></div><div class="gmail_extra">Hope that clears things up a bit. It takes a bit of staring at the first time, but I find it helpful to think of it as being analogous to a 're-throw' in an exception handler: Returning success (i.e. TypedError()) means continue the function, anything else means re-throw the error.</div></div></div></div></blockquote><div><br></div></span><div>OK got it now, the “empty” <span style="font-family:monospace,monospace">TypedError()</span>is the key :)</div><div>(and I was using success/failure terminology reversed compare to you)</div><div><br></div><div>— </div><span><font color="#888888"><div>Mehdi</div><div><br></div></font></span></div><div><div><div><br><blockquote type="cite"><div><div dir="ltr"><div><div class="gmail_extra"><br></div><div class="gmail_extra">Cheers,</div><div class="gmail_extra">Lang.</div><div class="gmail_extra"><br><div class="gmail_quote">On Tue, Feb 2, 2016 at 10:56 PM, Mehdi Amini <span dir="ltr"><<a href="mailto:mehdi.amini@apple.com" target="_blank">mehdi.amini@apple.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex"><div style="word-wrap:break-word"><br><div><span><blockquote type="cite"><div>On Feb 2, 2016, at 10:42 PM, Lang Hames <<a href="mailto:lhames@gmail.com" target="_blank">lhames@gmail.com</a>> wrote:</div><br><div><div dir="ltr">Hi Mehdi,<div><br></div><div><div>> I’m not sure to understand this claim? You are supposed to be able to extend and subclass the type of diagnostics? (I remember doing it for an out-of-tree LLVM-based project).<br></div><div><br></div><div>You can subclass diagnostics, but subclassing (on its own) only lets you change the behaviour of the diagnostic/error itself. What we need, and what this patch supplies, is a way to choose a particular handler based on the type of the error. </div></div></div></div></blockquote><div><br></div></span><div>If you subclass a diagnostic right now, isn’t the RTTI information available to the handler, which can then achieve the same dispatching / custom handling per type of diagnostic?</div><div>(I’m not advocating the diagnostic system, which I found less convenient to use than what you are proposing)</div><span><br><blockquote type="cite"><div><div dir="ltr"><div><div>For that you need RTTI, so this patch introduces a new RTTI scheme that I think is more suitable for errors types*, since unlike LLVM's existing RTTI system it doesn't require you to enumerate the types up-front.<br></div></div></div></div></blockquote><div><br></div></span><div>It looks like I’m missing a piece of something as it is not clear why is this strictly needed. I may have to actually look closer at the code itself.</div><div><div><br><blockquote type="cite"><div><div dir="ltr"><div><br></div><div>* If this RTTI system is considered generically useful it could be split out into its own utility. It's slightly higher cost than LLVM's system: One byte of BSS per type, and a walk from the dynamic type of the error to the root of the type-hierarchy (with possible early exit) for each type check.</div><div class="gmail_extra"><br></div><div class="gmail_extra">> What does success or failure means for the handler?</div><div class="gmail_extra"><br></div><div class="gmail_extra">It gives the handler an opportunity to inspect and then "re-throw" an error if necessary: A handler might not know whether it can recover based on type alone, or it may not want to recover at all, but instead attach some context to provide a richer diagnostic.</div><div class="gmail_extra"><br></div><div class="gmail_extra">As a concrete example, one of our motivating cases is processing object files in archives. Down in the object file processing code, a load command might be found to be malformed, but at that point there's no context to tell us that the object that it's in is part of an archive, so the best diagnostic we could produce is "In foo.o: malformed load command at index N". A (straw-man) improved system might look like this:</div><div class="gmail_extra"><br></div><div class="gmail_extra"><font face="monospace, monospace">class ObjectError ... { // <- Root of all object-file errors</font></div><div class="gmail_extra"><font face="monospace, monospace">  std::string ArchiveName = "";</font></div><div class="gmail_extra"><font face="monospace, monospace">  std::string ObjectName = "";</font></div><div class="gmail_extra"><font face="monospace, monospace">  std::error_code EC;</font></div><div class="gmail_extra"><font face="monospace, monospace"><br></font></div><div class="gmail_extra"><font face="monospace, monospace">  </font><span style="font-family:monospace,monospace">void log(raw_ostream &OS) const override {</span></div><div class="gmail_extra"><font face="monospace, monospace">    if (!ArchiveName.empty())</font></div><div class="gmail_extra"><font face="monospace, monospace">      OS << "In archive '" << ArchiveName << "', ";</font></div><div class="gmail_extra"><font face="monospace, monospace">    OS << "In object file '" << ObjectName << "', " << EC.message();</font></div><div class="gmail_extra"><font face="monospace, monospace">  }</font></div><div class="gmail_extra"><font face="monospace, monospace">};</font></div><div class="gmail_extra"><font face="monospace, monospace"><br></font></div><div class="gmail_extra"><font face="monospace, monospace">TypedError processArchive(Archive &A) {</font></div><div class="gmail_extra"><font face="monospace, monospace">  TypedError Err;</font></div><div class="gmail_extra"><font face="monospace, monospace">  for (auto &Obj : A) {</font></div><div class="gmail_extra"><font face="monospace, monospace">    auto Err = processObject(Obj);</font></div><div class="gmail_extra"><font face="monospace, monospace">    if (auto E2 =</font></div><div class="gmail_extra"><font face="monospace, monospace">      catchTypedErrors(std::move(Err),</font></div><div class="gmail_extra"><font face="monospace, monospace">        handleTypedError<ObjectError>([&](std::unique_ptr<ObjectError> OE) {</font></div><div class="gmail_extra"><font face="monospace, monospace">          OE->ArchiveName = A.getName();</font></div><div class="gmail_extra"><font face="monospace, monospace">          return TypedError(std::move(OE));</font></div><div class="gmail_extra"><font face="monospace, monospace">        })</font><span style="font-family:monospace,monospace">)</span></div><div class="gmail_extra"><font face="monospace, monospace">     return E2;</font></div><div class="gmail_extra"><font face="monospace, monospace">  }</font></div><div class="gmail_extra"><font face="monospace, monospace">}</font></div><div class="gmail_extra"><br></div><div class="gmail_extra">In this example, any error (whether an ObjectError or something else) will be intercepted by the 'catchTypedErrors' function. If the error *isn't* an ObjectError it'll be returned unmodified out of catchTypedErrors, triggering an immediate return from processArchive. If it *is* an ObjectError then the handler will be run, giving us an opportunity to tag the error as having occurred within archive A.</div><div class="gmail_extra"><br></div><div class="gmail_extra">Again - this is a straw-man example: I think we can do better again for diagnostics of this kind, but it showcases the value of being able to modify errors while they're in-flight.</div></div></div></blockquote><div><br></div></div></div><div>Sure, this case shows “success” of the handler, now what is a failure of the handler and how is it handled?</div><span><div><br></div><blockquote type="cite"><div><div dir="ltr"><div class="gmail_extra"><br></div><div class="gmail_extra"><br></div><div class="gmail_extra">> Is your call to catchAllTypedErrors(…) actually like a switch on the type of the error? What about a syntax that looks like a switch?<br></div><div class="gmail_extra"><div>></div><div>> switchErr(std::move(Err))</div><div>>    .case< MyCustomError>([] () { /* … */ })</div><div>>    .case< MyOtherCustomError>([] () { /* … */ })</div><div>>    .default([] () { /* … */ })</div><div><br></div><div>It's similar to a switch, but it's actually more like a list of regular C++ exception catch blocks (the name 'catchTypedError' is a nod to this).</div><div>The big difference is that you're not trying to find "the matching handler" in the set of options. Instead, the list of handlers is evaluated in order until one is found that fits, then that handler alone is executed. So if you had the following:</div><div><br></div><div><font face="monospace, monospace">class MyBaseError : public TypedErrorInfo<MyBaseError> {};</font></div><div><font face="monospace, monospace">class MyDerivedError : public TypedErrorInfo<MyDerivedError, MyBaseError> {}; // <- MyDerivedError inherits from MyBaseError.</font></div><div><br>and you wrote something like this:</div><div><br><span style="font-family:monospace,monospace">catchTypedErrors(std::move(Err),</span><br></div><div><font face="monospace, monospace">  handleTypedError<MyBaseError>([&](std::unique_ptr<MyBaseError> B) {</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">  }),</font></div><div><font face="monospace, monospace">  handleTypedError<MyDerivedError>([&](std::unique_ptr<MyDerivedError> D) {</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">  })</font></div><div><font face="monospace, monospace">);</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"><br></font></div><div>The second handler will never run: All 'Derived' errors are 'Base' errors, the first handler fits, so it's the one that will be run.</div><div><br></div><div>We could go for something more like a switch, but then you have to define the notion of "best fit" for a type, which might be difficult (especially if I extend this to support multiple inheritance in error hierarchies. ;). I think it's easier to reason about "first handler that fits”.</div></div></div></div></blockquote><div><br></div></span><div>Oh I was seeing it as a “first match as well”, just bike shedding the syntax as the function calls with a long flat list of lambdas as argument didn’t seem like the best we can do at the first sight.</div><div><br></div><div>— </div><span><font color="#888888"><div>Mehdi</div></font></span><div><div><div><br></div><br><blockquote type="cite"><div><div dir="ltr"><div class="gmail_extra"><div><br></div><div>Cheers,</div><div>Lang.</div><div><br></div><div><br></div></div><div class="gmail_extra"><div class="gmail_quote">On Tue, Feb 2, 2016 at 6:33 PM, Mehdi Amini <span dir="ltr"><<a href="mailto:mehdi.amini@apple.com" target="_blank">mehdi.amini@apple.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex"><div style="word-wrap:break-word"><div>Hi Lang,</div><div><br></div><div>I’m glad someone tackle this long lived issue :)</div><div>I’ve started to think about it recently but didn’t as far as you did!</div><div><br></div><div><span><blockquote type="cite"><div>On Feb 2, 2016, at 5:29 PM, Lang Hames via llvm-dev <<a href="mailto:llvm-dev@lists.llvm.org" target="_blank">llvm-dev@lists.llvm.org</a>> wrote:</div><br><div><div dir="ltr"><span style="font-size:13px">Hi All,</span><div style="font-size:13px"><br></div><div style="font-size:13px">I've been thinking lately about how to improve LLVM's error model and error reporting. A lack of good error reporting in Orc and MCJIT has forced me to spend a lot of time investigating hard-to-debug errors that could easily have been identified if we provided richer error information to the client, rather than just aborting. Kevin Enderby has made similar observations about the state of libObject and the difficulty of producing good error messages for damaged object files. I expect to encounter more issues like this as I continue work on the MachO side of LLD. I see tackling the error modeling problem as a first step towards improving error handling in general: if we make it easy to model errors, it may pave the way for better error handling in many parts of our libraries.</div><div style="font-size:13px"><br></div><div style="font-size:13px">At present in LLVM we model errors with std::error_code (and its helper, ErrorOr) and use diagnostic streams for error reporting. Neither of these seem entirely up to the job of providing a solid error-handling mechanism for library code. Diagnostic streams are great if all you want to do is report failure to the user and then terminate, but they can't be used to distinguish between different kinds of errors</div></div></div></blockquote><div><br></div></span><div>I’m not sure to understand this claim? You are supposed to be able to extend and subclass the type of diagnostics? (I remember doing it for an out-of-tree LLVM-based project).</div><span><div><br></div><br><blockquote type="cite"><div><div dir="ltr"><div style="font-size:13px">, and so are unsuited to many use-cases (especially error recovery). On the other hand, std::error_code allows error kinds to be distinguished, but suffers a number of drawbacks:</div><div style="font-size:13px"><br></div><div style="font-size:13px">1. It carries no context: It tells you what went wrong, but not where or why, making it difficult to produce good diagnostics.</div><div style="font-size:13px">2. It's extremely easy to ignore or forget: instances can be silently dropped.</div><div style="font-size:13px">3. It's not especially debugger friendly: Most people call the error_code constructors directly for both success and failure values. Breakpoints have to be set carefully to avoid stopping when success values are constructed.</div><div style="font-size:13px"><br></div><div style="font-size:13px">In fairness to std::error_code, it has some nice properties too:</div><div style="font-size:13px"><br></div><div style="font-size:13px">1. It's extremely lightweight.</div><div style="font-size:13px">2. It's explicit in the API (unlike exceptions).</div><div style="font-size:13px">3. It doesn't require C++ RTTI (a requirement for use in LLVM).</div><div style="font-size:13px"><br></div><div style="font-size:13px">To address these shortcomings I have prototyped a new error-handling scheme partially inspired by C++ exceptions. The aim was to preserve the performance and API visibility of std::error_code, while allowing users to define custom error classes and inheritance relationships between them. My hope is that library code could use this scheme to model errors in a meaningful way, allowing clients to inspect the error information and recover where possible, or provide a rich diagnostic when aborting.</div><div style="font-size:13px"><br></div><div style="font-size:13px">The scheme has three major "moving parts":</div><div style="font-size:13px"><br></div><div style="font-size:13px">1. A new 'TypedError' class that can be used as a replacement for std::error_code. E.g.</div><div style="font-size:13px"><br></div><div style="font-size:13px"><font face="monospace, monospace">std::error_code foo();</font></div><div style="font-size:13px"><br></div><div style="font-size:13px">becomes</div><div style="font-size:13px"><br></div><div style="font-size:13px"><font face="monospace, monospace">TypedError foo();</font></div><div style="font-size:13px"><br></div><div style="font-size:13px">The TypedError class serves as a lightweight wrapper for the real error information (see (2)). It also contains a 'Checked' flag, initially set to false, that tracks whether the error has been handled or not. If a TypedError is ever destructed without being checked (or passed on to someone else) it will call std::terminate(). TypedError cannot be silently dropped.</div></div></div></blockquote><div><br></div></span><div>I really like the fact that not checking the error triggers an error (this is the "hard to misuse” part of API design IMO).</div><div>You don’t mention it, but I’d rather see this “checked” flag compiled out with NDEBUG.</div><span><br><blockquote type="cite"><div><div dir="ltr"><div style="font-size:13px"><br></div><div style="font-size:13px">2. A utility class, TypedErrorInfo, for building error class hierarchies rooted at 'TypedErrorInfoBase' with custom RTTI. E.g.</div><div style="font-size:13px"><br></div><div style="font-size:13px"><font face="monospace, monospace">// Define a new error type implicitly inheriting from TypedErrorInfoBase.</font></div><div style="font-size:13px"><font face="monospace, monospace">class MyCustomError : public TypedErrorInfo<MyCustomError> {</font></div><div style="font-size:13px"><font face="monospace, monospace">public:</font></div><div style="font-size:13px"><font face="monospace, monospace">  // Custom error info.</font></div><div style="font-size:13px"><font face="monospace, monospace">};</font></div><div style="font-size:13px"><font face="monospace, monospace"><br></font></div><div style="font-size:13px"><font face="monospace, monospace">// Define a subclass of MyCustomError.</font></div><div style="font-size:13px"><font face="monospace, monospace">class MyCustomSubError : public TypedErrorInfo<MyCustomSubError, MyCustomError> {</font></div><div style="font-size:13px"><font face="monospace, monospace">public:</font></div><div style="font-size:13px"><font face="monospace, monospace">  // Extends MyCustomError, adds new members.</font></div><div style="font-size:13px"><font face="monospace, monospace">};</font></div><div style="font-size:13px"><br></div><div style="font-size:13px">3.  A set of utility functions that use the custom RTTI system to inspect and handle typed errors. For example 'catchAllTypedErrors' and 'handleTypedError' cooperate to handle error instances in a type-safe way:</div><div style="font-size:13px"><br></div><div style="font-size:13px"><font face="monospace, monospace">TypedError foo() {</font></div><div style="font-size:13px"><font face="monospace, monospace">  if (SomeFailureCondition)</font></div><div style="font-size:13px"><font face="monospace, monospace">    return make_typed_error<MyCustomError>();</font></div><div style="font-size:13px"><font face="monospace, monospace">}</font></div><div style="font-size:13px"><font face="monospace, monospace"><br></font></div><div style="font-size:13px"><font face="monospace, monospace">TypedError Err = foo();</font></div><div style="font-size:13px"><font face="monospace, monospace"><br></font></div><div style="font-size:13px"><font face="monospace, monospace">catchAllTypedErrors(std::move(Err),</font></div><div style="font-size:13px"><font face="monospace, monospace">  handleTypedError<MyCustomError>(</font></div><div style="font-size:13px"><font face="monospace, monospace">    [](std::unique_ptr<MyCustomError> E) {</font></div><div style="font-size:13px"><font face="monospace, monospace">      // Handle the error.</font></div><div style="font-size:13px"><font face="monospace, monospace">      return TypedError(); // <- Indicate success from handler.</font></div></div></div></blockquote><div><br></div></span><div>What does success or failure means for the handler?</div><span><div><br></div><br><blockquote type="cite"><div><div dir="ltr"><div style="font-size:13px"><font face="monospace, monospace">    }</font></div><div style="font-size:13px"><font face="monospace, monospace">  )</font></div><div style="font-size:13px"><font face="monospace, monospace">);</font></div><div style="font-size:13px"><br></div><div style="font-size:13px"><br></div><div style="font-size:13px">If your initial reaction is "Too much boilerplate!" I understand, but take comfort: (1) In the overwhelmingly common case of simply returning errors, the usage is identical to std::error_code:</div><div style="font-size:13px"><br></div><div style="font-size:13px"><font face="monospace, monospace">if (TypedError Err = foo())</font></div><div style="font-size:13px"><font face="monospace, monospace">  return Err;</font></div><div style="font-size:13px"><br></div><div style="font-size:13px">and (2) the boilerplate for catching errors is usually easily contained in a handful of utility functions, and tends not to crowd the rest of your source code. My initial experiments with this scheme involved updating many source lines, but did not add much code at all beyond the new error classes that were introduced.</div><div style="font-size:13px"><br></div><div style="font-size:13px"><br></div><div style="font-size:13px">I believe that this scheme addresses many of the shortcomings of std::error_code while maintaining the strengths: </div><div style="font-size:13px"><br></div><div style="font-size:13px">1. Context - Custom error classes enable the user to attach as much contextual information as desired.</div><div style="font-size:13px"><br></div><div style="font-size:13px">2. Difficult to drop - The 'checked' flag in TypedError ensures that it can't be dropped, it must be explicitly "handled", even if that only involves catching the error and doing nothing.</div><div style="font-size:13px"><br></div><div style="font-size:13px">3. Debugger friendly - You can set a breakpoint on any custom error class's constructor to catch that error being created. Since the error class hierarchy is rooted you can break on TypedErrorInfoBase::TypedErrorInfoBase to catch any error being raised.</div><div style="font-size:13px"><br></div><div style="font-size:13px">4. Lightweight - Because TypedError instances are just a pointer and a checked-bit, move-constructing it is very cheap. We may also want to consider ignoring the 'checked' bit in release mode, at which point TypedError should be as cheap as std::error_code.</div></div></div></blockquote><div><br></div></span><div>Oh here you mention compiling out the “checked” flag :)</div><span><br><blockquote type="cite"><div><div dir="ltr"><div style="font-size:13px"><br></div><div style="font-size:13px">5. Explicit - TypedError is represented explicitly in the APIs, the same as std::error_code.</div><div style="font-size:13px"><br></div><div style="font-size:13px">6. Does not require C++ RTTI - The custom RTTI system does not rely on any standard C++ RTTI features.</div><div style="font-size:13px"><br></div><div style="font-size:13px">This scheme also has one attribute that I haven't seen in previous error handling systems (though my experience in this area is limited): Errors are not copyable, due to ownership semantics of TypedError. I think this actually neatly captures the idea that there is a chain of responsibility for dealing with any given error. Responsibility may be transferred (e.g. by returning it to a caller), but it cannot be duplicated as it doesn't generally make sense for multiple people to report or attempt to recover from the same error.</div><div style="font-size:13px"><br></div><div style="font-size:13px">I've tested this prototype out by threading it through the object-creation APIs of libObject and using custom error classes to report errors in MachO headers. My initial experience is that this has enabled much richer error messages than are possible with std::error_code.</div><div style="font-size:13px"><br></div><div style="font-size:13px">To enable interaction with APIs that still use std::error_code I have added a custom ECError class that wraps a std::error_code, and can be converted back to a std::error_code using the typedErrorToErrorCode function. For now, all custom error code classes should (and do, in the prototype) derive from this utility class. In my experiments, this has made it easy to thread TypedError selectively through parts of the API. Eventually my hope is that TypedError could replace std::error_code for user-facing APIs, at which point custom errors would no longer need to derive from ECError, and ECError could be relegated to a utility for interacting with other codebases that still use std::error_code.</div><div style="font-size:13px"><br></div><div style="font-size:13px">So - I look forward to hearing your thoughts. :)</div></div></div></blockquote><br></span><div>Is your call to catchAllTypedErrors(…) actually like a switch on the type of the error? What about a syntax that looks like a switch?</div><div><br></div><div>switchErr(std::move(Err))</div><div>    .case< MyCustomError>([] () { /* … */ })</div><div>    .case< MyOtherCustomError>([] () { /* … */ })</div><div>    .default([] () { /* … */ })</div><div><br></div><div><br></div><div>— </div><div>Mehdi       </div><br><blockquote type="cite"><div><span><div dir="ltr"><div style="font-size:13px"><br></div><div style="font-size:13px">Cheers,</div><div style="font-size:13px">Lang.</div><div style="font-size:13px"><br></div><div style="font-size:13px"><div>Attached files:<br></div><div><br></div><div>typed_error.patch - Adds include/llvm/Support/TypedError.h (also adds anchor() method to lib/Support/ErrorHandling.cpp).</div><div><br></div><div>error_demo.tgz - Stand-alone program demo'ing basic use of the TypedError API.</div></div><div style="font-size:13px"><br></div><div style="font-size:13px">libobject_typed_error_demo.patch - Threads TypedError through the binary-file creation methods (createBinary, createObjectFile, etc). Proof-of-concept for how TypedError can be integrated into an existing system.</div><div style="font-size:13px"><br></div></div>
</span><span><typed_error.patch></span><span><error_demo.tgz></span><span><thread_typederror_through_object_creation.patch></span>_______________________________________________<span><br>LLVM Developers mailing list<br><a href="mailto:llvm-dev@lists.llvm.org" target="_blank">llvm-dev@lists.llvm.org</a><br><a href="http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev" target="_blank">http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev</a><br></span></div></blockquote></div><br></div></blockquote></div><br></div></div>
</div></blockquote></div></div></div><br></div></blockquote></div><br></div></div></div>
</div></blockquote></div><br></div></div></div></blockquote></div><br></div>
</div></blockquote></div></div></div><br></div></blockquote></div><br></div>
</div></blockquote></div></div></div><br></div></blockquote></div><br></div></div></div>