News for hackers. Stuff that matters.
Re:Much Ado About None: Sentinel Values in Python by nomel on Saturday October 31, 2026 @07:17PM (#50321192)
How are these two things related? If you don't use exceptions, you just have to implement them yourself by manually bubbling up the error, all the way up the stack, rather than using the dedicated side channel to do so.

The problem I see is there's no way to enforce the check that you actually handled/bubbled up the error. With exceptions, you just crash if you don't handle it, which is much preferred if determinism is the goal. Is there some way to use the type system to do this? Maybe incoming are UnhandledResult outgoing are some HandledResult or something like that?

Re:Much Ado About None: Sentinel Values in Python by aw1621107 on Saturday October 31, 2026 @08:09PM (#50321518)
If you're going to use the returned value you'd probably return the moral equivalent of Rust's Result/Haskell's Either/etc. types. As long as the returned types don't happen to share the necessary API(s) the type checker should yell at you if you don't branch on the returned value.

I don't think Python has the equivalent of C++'s [[nodiscard]] or Rust's #[must_use], though, so if there are cases where the return value can legitimately be ignored then return values risk missing errors that would show up under exceptions.

Re:Much Ado About None: Sentinel Values in Python by travisd on Sunday November 01, 2026 @06:20PM (#50330941)
Yeah, this is basically what we do. It ends up a bit more ergonomic to not have explicit result types because union types are pretty easy in Python. For example, we have functions like

    # courses.py
    def create(...) -> Course | ErrInvalid:
and they get used like

    match courses.create(...):
        case courses.Course() as course:
            # We use `course` below.
            pass
        case courses.ErrInvalid() as err:
            # Error conditions are usually translated into early returns
            raise fastapi.HTTPException(
                status.HTTP_422_UNPROCESSABLE_ENTITY, err.message
            )
    
    return APICourseCreateResponse(id=course.id, ...)
It has worked (for us at least) pretty well to reduce the number of unhandled errors that make their way into Sentry. The granularity of the error types depends on the need (e.g., sometimes we just have an `ErrInvalid` that returns a message which gets displayed to the user, but sometimes we'll have ErrInvalidAddress or ErrInvalidTaxCode to be more specific for the context).

And yeah, without nodiscard, we try™ to only do this on functions that return values that the caller will definitely need to use. Also combined with exhaustiveness checks on switch statements by default.