How should a function report an error?

Functions can fail. I have functions that try to find a steady state of a DAE system, and that can fail. What is better:

  • to raise an exception
  • to return nothing

While this error can be fatal, it does not have to be, trying again with different parameters might help.

Extra question:
Should a linter give a warning if a function returns a value or nothing?

2 Likes

On this question, I really appreciate the design of Roots.jl. Its high-level API is opinionated and throws on failure, while its lower-level API gives advanced users more control over how to handle non-convergence.

I think that is a very good pattern. A high-level function should usually be safe and explicit by default. But lower-level interfaces can expose alternative failure-handling strategies for users who really need them.

If you do choose to throw, I would also recommend using a custom exception type. That way, downstream code can still recover cleanly with try/catch when appropriate, without having to inspect error strings or catch unrelated exceptions.

So for me, it depends on the API’s abstraction level.

3 Likes

For me, nothing would be too uninformative.

Similar to Roots.jl, I also prefer for internal functions to maybe also just return the error or in some way information what/why things failed – and the high level parts to throw those.

I could not find any lower level API in the documentation of Roots.jl. Which functions are you talking about? How do they report a failure?

Unlike find_zero, solve will return NaN on non-convergence.

And find_zero will throw Roots.ConvergenceFailed.

1 Like

I think I will return the tuple <result>, error. I cannot use NaN because my result is not a number. Then the caller can decide to print a warning in case of an error, or to retry with different parameters.