Remix.run Logo
nine_k 2 hours ago

At Facebook they name certain "escape hatch" functions in a way that inescapably make them look like a GIANT EYESORE. Stuff like DANGEROUSLY_CAST_THIS_TO_THAT, or INVOKE_SUPER_EXPENSIVE_ACTION_SEE_YOU_ON_CODE_REVIEW. This really drives home the point that such things must not be used except in rare extraordinary cases.

If unwrap() were named UNWRAP_OR_PANIC(), it would be used much less glibly. Even more, I wish there existed a super strict mode when all places that can panic are treated as compile-time errors, except those specifically wrapped in some may_panic_intentionally!() or similar.

adzm 2 hours ago | parent | next [-]

> make them look like a GIANT EYESORE

React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED comes to mind. I did have to reach to this before, but it certainly works for keeping this out of example code and other things like reading other implementations without the danger being very apparent.

At some point it was renamed to __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE which is much less fun.

17 minutes ago | parent [-]
[deleted]
Nathanba 2 hours ago | parent | prev [-]

right and if the language designers named it UNWRAP_OR_PANIC() then people would rightfully be asking why on earth we can't just use a try-catch around code and have an easier life

nine_k 2 hours ago | parent | next [-]

But a panic can be caught and handled safely (e.g. via std:: panic tools). I'd say that this is the correct use case for exceptions (ask Martin Fowler, of all people).

There is already a try/catch around that code, which produces the Result type, which you can presumptuously .unwrap() without checking if it contains an error.

Instead, one should use the question mark operator, that immediately returns the error from the current function if a Result is an error. This is exactly similar to rethrowing an exception, but only requires typing one character, the "?".

yoyohello13 2 hours ago | parent | prev | next [-]

Probably not, since errors as values are way better than exceptions.

nomel 2 hours ago | parent [-]

How so? An exception is a value that's given the closest, conceptually appropriate, point that was decided to handle the value, allowing you to keep your "happy path" as clean code, and your "exceptional circumstances" path at the level of abstraction that makes sense.

It's way less book-keeping with exceptions, since you, intentionally, don't have to write code for that exceptional behavior, except where it makes sense to. The return by value method, necessarily, implements the same behavior, where handling is bubbled up to the conceptually appropriate place, through returns, but with much more typing involved. Care is required for either, since not properly bubbling up an exception can happen in either case (no re-raise for exceptions, no return after handling for return).

yoyohello13 2 hours ago | parent | next [-]

There are many many pages of text discussing this topic, but having programmed in both styles, exceptions make it too easy for programmer to simply ignore them. Errors as values force you to explicitly handle it there, or toss it up the stack. Maybe some other languages have better exception handling but in Python it’s god awful. In big projects you can basically never know when or how something can fail.

nomel an hour ago | parent [-]

I would claim the opposite. If you don't catch an exception, you'll get a halt.

With return values, you can trivially ignore an exception.

    let _ = fs::remove_file("file_doesn't_exist");

    or

    value, error = some_function()
    // carry on without doing anything with error
In the wild, I've seen far more ignoring return errors, because of the mechanical burden of having type handling at every function call.

This is backed by decades of writing libraries. I've tried to implement libraries without exceptions, and was my admittedly cargo-cult preference long ago, but ignoring errors was so prevalent among the users of all the libraries that I now always include a "raise" type boolean that defaults to True for any exception that returns an error value, to force exceptions, and their handling, as default behavior.

> In big projects you can basically never know when or how something can fail.

How is this fundamentally different than return value? Looking at a high level function, you can't know how it will fail, you just know it did fail, from the error being bubbled up through the returns. The only difference is the mechanism for bubbling up the error.

Maybe some water is required for this flame war. ;)

yoyohello13 14 minutes ago | parent [-]

I can agree to disagree :)

pyrolistical 2 hours ago | parent | prev [-]

Exception is hidden control flow, where as error values are not.

That is the main reason why zig doesn’t have exceptions.

nomel 2 hours ago | parent [-]

I'd categorize them more as "event handlers" than "hidden". You can't know where the execution will go at a lower level, but that's the entire point: you don't care. You put the handlers at the points where you care.

sfink 2 hours ago | parent | prev [-]

...and you can? try-catch is usually less ergonomic than the various ways you can inspect a Result.

    try {
      data = some_sketchy_function();
    } catch (e) {
      handle the error;
    }
vs

    result = some_sketchy_function();
    if let Err(e) = result {
      handle the error;
    }
Or better yet, compare the problematic cases where the error isn't handled:

    data = some_sketchy_function();
vs

    data = some_sketchy_function().UNWRAP_OR_PANIC();
In the former (the try-catch version that doesn't try or catch), the lack of handling is silent. It might be fine! You might just depend on your caller using `try`. In the latter, the compiler forces you to use UNWRAP_OR_PANIC (or, in reality, just unwrap) or `data` won't be the expected type and you will quickly get a compile failure.

What I suspect you mean, because it's a better argument, is:

    try {
        sketchy_function1();
        sketchy_function2();
        sketchy_function3();
        sketchy_function4();
    } catch (e) {
        ...
    }
which is fair, although how often is it really the right thing to let all the errors from 4 independent sources flow together and then get picked apart after the fact by inspecting `e`? It's an easier life, but it's also one where subtle problems constantly creep in without the compiler having any visibility into them at all.