Hacker Newsnew | past | comments | ask | show | jobs | submit | maleldil's commentslogin

What's the difference between an implicit error and exceptions? Being explicit about errors is good. Go's syntactical implementation, coupled with its unexpressive type system, is the problem.

I will freely go on the record as saying that there's nothing wrong with exceptions for this exact reason: errors are so common that a function being "pure" is the exception, and that errors-as-value handling invariable turns into an endless chain of something like "if err; return (nil/zero, err)" in every language which tries it.

The same would apply to anytime you have Result types - ultimately its still just syntactic sugar over "if err then...".

What's far more common in real programs is that an error can occur somewhere where you do not have enough context to handle or resolve it, or you're unaware it can happen. In which case the concept of exceptions is much more valid: "if <bad thing here> what do I want to do?" usually only has a couple of places you care about the answer (i.e. "bad thing happened during business process, so start unwinding that process" and many more where the answer is either "crash" or "log it and move on to the next item".


Exceptions can be bad if done the wrong way. But the solution isn’t to not deal with it and put it on the programmer. That’s laziness.

The problems are that the signature of functions doesn’t say anything about what values it might throw, and that sometimes the control flow is obscured — an innocuous call throws.

Both of these are solvable.


Sure but that also feels like a compiler problem. The compiler knows everywhere my function can go. So rather then having it just throw an exception - i.e. arbitrary data - on the stack, surely what's really happening is I'm creating a big union of "result | error[type,type,type,type]" which only gets culled when I add my "exception" handling.

My argument here would be, that all of this though doesn't need to be seen unless its relevant - it seems reasonable that the programmer should be able to write code for the happy path, implicitly understanding there's an error path they should be aware of because errors always happen (I mean, you can straight up run out of memory almost anywhere, for example).


I agree, and I think that the simplicity mantra of the early go team caused them to not deal with solvable problems when they had the chance.

They would rather not solve it, thinking that the "programmers will deal with it".

Now they claim it’s too late.


That depends on what you're using. If you're using Pydantic, which lets you define a struct-like data type with validation, you can tell it to validate assignments as well [1]. Or you can set the class as frozen and forbid assignment entirely [2].

However, if you mean annotating a local variable with a type, then no, nothing will stop it at runtime. If you use a type checker, though, it will tell you that statically.

The ecosystem also offers other runtime validation options, such as beartype [3]. For example, you can annotate a function such that it always checks the data types of input parameters when called. You can even apply this to a whole module if you want, but I don't think that's commonly done.

[1] https://docs.pydantic.dev/latest/api/config/#pydantic.config...

[2] https://docs.pydantic.dev/latest/api/config/#pydantic.config...

[3] https://beartype.readthedocs.io/en/latest/eli5/


Checking types on all function calls adds a considerable amount of extra work that I personally am not willing to pay, especially since static type checkers exist.

Me neither! I was just mentioning it as a possibility. My main use of beartype is `is_bearable` for runtime checking of specific data structures, in cases where `isinstance` isn't quite enough. I would still explore turning full checks during tests, though [1].

[1] https://github.com/beartype/pytest-beartype


Why do you say it's unstable?

Take async for example. You have to choose some third-party async runtime which may or may not work with other runtimes, libraries, platforms, etc.

With Go, async code written in Go 1.0 compiles and runs the same in Go 1.26, and there is no fragmentation or necessity to reach for third party components.


It is literally being changed constantly. They chose to add it to the Linux kernel but they have to build the features they need into the language as they go. It's not backward compatible like C and C++ either. Sure, there are a couple of versions you could try to pin to in Rust, but then all your libraries need to be pinned to the same version (to be fair, this last part is an assumption).

I think very nearly every assertion in this statement is demonstrably untrue.

It's not, especially the part about it being actively changed to adapt to Linux. I've heard several complaints about this already. I may have been a little harsh on the stability concerns but I stand by my assertions in general.

They’re not changing the language, they’re stabilizing new capabilities to it. Golang is stabilizing new features too in every release.

I think the relevant problem of rust to this thread is not that it's unstable (it isn't unstable!) but it can be argued that it's a moving target

Yes, because you need evidence from small studies to justify running larger ones. You're not going to run an n=1000 experiment from a mere guess.

I think the frustration is: it's not "news"

Annotating empty collections is one of the few places you need to annotate outside function signatures. It's not a big deal. It doesn't happen that often.

And, when it does, you can just put them when the empty container is assigned:

    things: set[tuple[str, str, int]] = set()
    users: list[User] = []
Many people don't seem to know this exists.

Yes, that's what I was referring to. I get it that Pyrefly wanted to advertise their approach here, but it's weird that they didn't at least acknowledge this. It's what I use because it works on every type check, and I don't need to rely on their particular implementation for this.

In fact, I recently migrated a project from Pyright to Pyrefly for performance reasons, and there was very little I had to change between. The most annoying thing was Pyrefly's lack of exhaustive pattern matching for StrEnum and Literal[...]


It's acknowledged at the end of the "infer any" strategy, but perhaps worded poorly.

> To improve type safety in these situations, type checkers that infer Any for empty containers can choose to generate extra type errors that warn the user about the insertion of an Any type. While this can reduce false negatives, it burdens developers by forcing them to explicitly annotate every empty container in order to silence the warnings.

ie: "type checkers that don't infer container types can emit an error and require users to annotate"


I missed that. At least pyright will only emit an error if `typeCheckingMode` is strict (which forbids `Unknown`). It will happily treat `Unknown` as `Any` in basic mode.

> TypeAlias, Generic

This is mitigated by modern (3.12+) generic and `type` syntax, which just looks like any other static language.


Showing the prompts is not feasible when using agentic coding tools. I suppose one could persist all chat logs ever used in the project, but is that even useful?

I think it would be useful. I see lots of comments like "it one-shotted this" and am curious if they just had to write one sentence or many pages of instructions.


It's a challenge for PHP programmers. I imagine the relevant people would recognise that format.

For reference, jq and python don't allow comments.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: