3 ms·
In TS you get literals, which is its own dimension of valuable tooling of course. So inferring as an untagged union is not wrong of course! It's just that if y
by rtpg 1y ago
In TS you get literals, which is its own dimension of valuable tooling of course.
So inferring as an untagged union is not wrong of course! It's just that if you are always inferring the type of an if expression to A | B, then this will also happen unintentionally a lot.
And so at the end of some code, when you actually use x, then you'll see an error like "expected usize, got usize | bool". In the case that this was a mistake, you're now looking at having to manually figure out why x was inferred this way.
In typescript your "if expression" is a ternary expression. Those are quite rare. In rust they're all over the place. Match statements are the same thing. Imagine having a 10 clause match statement and one of them unintentionally gives a different type. There's even just the classic "semicolon makes the branch into a ()"!
So always inferring a union across branches of an if expression or a match means that your type errors on genuine mistakes are almost never in the right spot.
Of course we can annotate intermediate values to find our way back. Annotating intermediate values in a chained expression is a bit miserable, but it is what it is.
Decent typescript tends to not have this problem because there are few syntactic structures where you need to evaluate multiple branches to figure out the type of an expression. And the one big example (return values)... well you want to be annotating the return value of your functions in general.
Rust is in a similar space for Result types, at least. But I don't think it generalizes at all. If you start inferring union types, and combine that with trait resolution, I _think_ that we'd end up with much less helpful error messages in the case of actual mistakes, because the actual location of the error will be harder to find.
let mut x = if some_condition { 1 } else { false }
// bunch of code
return f(x) // expected usize, got usize | bool
TS gets away with this stuff because JS's object model is simple (TS doesn't need to do any form of trait resolution!) and the opportunities to introduce unions implicitly are relatively few in TS code in general.
And this isn't even really getting into Rust needing to actually implement untagged unions if they had them! Implicit tagging feels off in a language very serious about not having expensive hidden abstractions. But how are you going to guarantee bit layout to allow for the differentiation here?
I'm saying all of this but I'd love it if someone showed up with a good untagged union proposal to Rust, because I _like_ the concept. Just feels intractable