Haskell has such situations as well, but usually far less verbose. Getting something to typecheck because you wrote down something incompatible uninferable still sucks. But far less than C++.
Haskell has such situations as well, but usually far less verbose. Getting something to typecheck because you wrote down something incompatible uninferable still sucks. But far less than C++.
If the Rust compiler can figure out what the type should be, why doesn't it just do the cross-function inference, and leave the complicated nested implications which only obscure the intent and effect out of it?
If having the programmer specify types is an important check on the correctness of the code that is written, how is blindly copying, without understanding some 60+ character type specification string from an error message going to help demonstrate correctness? All it does is make two sections "consistent". It isn't something the programmer understands or specifies as a type check.
It could! This is an explicit design choice. There are a few different reasons. They're all sort of connected...
In general, Rust takes the position that the type signature is the contract. If you inferred the types on function signatures, changing the body of your function could change the signature, which means that breaking changes are harder to detect. It also leads to "spooky action at a distance" errors; I could change a line of code in function A, but then the compiler complains about the body of some unrelated code in a totally different part of the codebase, because that changed the signature of function A, which changed the signature of function B, which is called in function C. My error shows C is wrong, but I made a mistake in the body of A. That's confusing. Much nicer to say "Hey you said the signature of A is X but the body is Y, something is wrong here."
I am gonna handwave this one slightly because I don't fully remember all of the details, but full program inference and subtyping is undecidable. Rust doesn't have subtyping in general for this and other reasons, but lifetimes do have subtyping. I am sure this would get ugly.
Speaking of getting ugly, Rust is already criticized often for compile times. Full program inference would make this much, much worse. Again with that changing signatures issue, cascading signature change would cause even more of your program to need to be recompiled, which means that bad error message is gonna take even longer to appear in the first place.
I think there might be more but those are the biggest ones off the top of my head.
Correct. Haskell is the only language I know of with globally decidable type inference, and uses the similar hindley-milner method as Rust... but no doubt some of Rust's language features can break global inference. In Haskell, many common language extensions can also break global inference.
I think if Haskell was written today they probably wouldn't pick global inference as a goal, Haskell "best practice" types the function boundaries in the same way that Rust enforces.
A lot of the "weirder" parts of Haskell are there because early on Haskell was pretty much "LazyML" and then it started growing into something different.
Suggestions should probably trim redundant prefixes like that, but recognizing standard library namespaces shouldn't be a big obstacle to understanding either.
It was also crystal clear that, like C++, Rust puts many barriers to true abstraction. You have to know many, many details of how a specific type is implemented, sometimes several levels deep, to correctly use it at a high level. The cognitive overhead is enormous.
Hiding them basically amounts to targeting a different domain. This is very different from being irrelevant and noisy.
For sure though, not every error has a great message and some can be cryptic. But those cases are relatively rare these days IMO
For what it's worth, I believe modern C++ compilers give much better error messages than older ones.
We're used to avoiding complexity when it comes to logic, but maybe there's less awareness when it comes to types. Maybe what we need is to have a bigger conversation around that so people realize it's something that needs to be on their radar.
The only immediate solution I can see is to keep types simple enough that the user can fit the entire relevant type-space in their head (and in the IDE dialog!), so that they themselves can determine which part is actually "wrong" (as opposed to just contradictory).
The talks were by Jason Turner, who has an ARM emulator implemented entirely as constexprs and a test suite that runs at compile time (so if it compiled, the tests passed). Obviously for actually interacting with it, its not running at compile time, but the logic has the ability to run at compile time, which is pretty cool.
"Old school" is asically the programming languages that originated in the 70s-80-90s. An incorrect but an illustrative way to describe error messages for them is "The programmer needs to suffer". They are any combination of cryptic, terse, complex, exposing internal machinery of the compilers and linkers etc. There are many reasons for this: computers were not powerful enough to afford better code analysis, parsing, backtracking etc; the users of the tools also knew the tools and could tinker with them etc.
"New school" is from late 2000s on. I usually say it started with Elm. Clear messages pinpointing the exact problem, solutions to problems inside error messages, error clarity as one of the priorities in language/compiler/tool design.
Category grammars were much better but more resource demanding, just like proper ASTs instead compiling as one goes.
Ironically there were already some attempts to Smalltalk like tooling for C++ back then, but again as you mention too much resource constraints.