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.
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).
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.
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].
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).
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.
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.
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.
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.
reply