Hacker News new | ask | show | jobs
by frou_dh 4155 days ago
> There's a macro called "try!(e)", which, if e returns a None value, returns from the enclosing function via a return you can't see in the source code. Such hidden returns are troubling.

Strikes me as simply a very appropriate use of macros. Get tired of writing the same syntactic fragment again and again? Write a macro. Want to see what some macro is "hiding"? Look it up or expand it.

2 comments

The issue is it makes it difficult to notice that a function might return when scanning through a function. Especially as it's in places that are looking for a value (e.g. assignment).

At the moment it's just return and try!, but people look to the standard library for what is acceptable. When the standard library contains a macro that can return, people will write their own macros that return. It could potentially be half a dozen different macros you need to keep in your head.

Personally, I go back and forth on it. Hopefully it will turn out fine.

> The issue is it makes it difficult to notice that a function might return when scanning through a function.

What other solutions are there? The only other approach to error handling I've seen is exceptions (e.g., C++, Java, C#, JS…), and if you don't like `try!` because it is a "hidden return", you certainly won't like exceptions. At least with Rust's macros, I know that in the absence of one, there is no return; in the presence, there might be. Exceptions in most languages make no guarantee.

It's a trade off. For the trouble of exceptions I get nice benefits like stack traces (though there are proposals to add stack traces to Rusts error handling). There are times when I really like exceptions, and times (such as trying to trace through an execution path) that I'm irritated by them.

What other solutions are there? Well, you could make the return explicit:

    let z = try!(x / y, onerror = return)
There are obvious downsides, such as added verbosity, and the need to figure out keyword arguments in macros, and do you allow access to the error value etc. but at least I can grep for/highlight "return". It also makes the meaning of the slightly confusingly named "try" more obvious (again, I'm vaguely aware of proposals that would change the name).

I think ultimately try! is a good thing, but I don't think it's trivially "a very appropriate use of macros". It's a considered use given some difficult trade-offs.

Kotlin has a notion of function inlining with the return statement being inlined too. This means you can do, e.g.:

list.forEach { if (it == something) return; }

and you aren't returning from the code block, but the enclosing function. Yet behind the scenes forEach is being expanded by the compiler into a regular imperative for loop, in a macro-like way.

I think people get used to it, especially if it's a part of the standard libs. C++ <iostream> overloads the bitshift operator and nobody seems to mind.
Why aren't exceptions used here?

Is this because the behavior is sort of "exceptional" but not so exceptional that the (supposedly inefficient) exception-mechanism is warranted?

In that case, I think the compiler should handle this case still. Using profiling, it could determine which exceptions are really exceptions and which ones are not.

Rust doesn't have exceptions.
Interesting. This is certainly new for me :)