Might place more requirements on both lang and lib than you were asking for, but I’ve been messing with a reterr (return error) keyword in my language Tiko that works alot like Rust’s try! macro/? operator.

reterr returns on any falsey expression, any Result<T, E> that is an Error, and any empty Option<T>. Anything else continues through. It will also pass the error up from Result iff the func’s type signature returns an Error.

I like it alot. It makes for some VERY clean code that reads declaratively but doesn’t seem out of place in the imperative style. Preconditions and Go-like if err != nil { return err } handling are readable but don’t take up the majority of visual space on screen.

I worry because it does quite alot of things. It blesses Result and Option, plus it just happens to work with falsey expressions which aren’t necessarily errors. It’s got utility, but not exactly the greatest in terms of aesthetics from a language design standpoint. reterr and return are also super similar visually and might be confused/missed. Since this is mainly a toy language it’s mostly just me having fun and I haven’t had much issue with it so maybe I’m just too hesitant to commit ;)

It’s definitely not a new idea (mostly ripped off from rejected ideas in the Rust ? operator RFC) but given your comment I thought it might be interesting enough to merit a reply.

Ah yeah, I’m most familiar with pattern matching in Scala and agree it’s pretty rad, but the Erlang way seems better in some ways since it groups cases together from a definition standpoint (?). I think Haskell does that too, and it makes for really clean stuff there.

Something I think important in this kind of discussion is a bit more thought to what to do if your preconditions fail.

I’ve seen code in the past which seems to have evolved along the lines of:

use this arg passed in

have a crash report, arg was null

wrap function body in “if (arg != null) { … }” (with no ‘else’)

crash fixed, yay - commit and move on

Now rearranging that as a guard clause gets rid of one annoyance (code indent etc) but if the action taken if the guard clause fails is just “return”, we’ve missed the opportunity to fix something.

If some of the args don’t meet some criteria, that might be more reasonably an exception or panic() - it could well be a logic error in some other part of the program. Some times it makes sense for a function to be a no-op, but it’s pretty rare (the ‘draft timer’ in the article).

When a failed guard condition raises an immediate failure, that’s called a contract precondition. The general rule I’m familiar with is “return and clean up if the guard might be false, throw an error if the guard shouldn’t be false.”

You’re right, in most cases you’ll need to throw in a guard clause instead of return. You only return when failed preconditions means you don’t need to do any actions, not can’t do. Like deleting a resource when it doesn’t exist.