6 ms·
Bottom types get difficult in presence of statically dispatched type classes / traits. Even though the bottom type doesn't have values, because it is an existin
by GolDDranks 6y ago
Bottom types get difficult in presence of statically dispatched type classes / traits. Even though the bottom type doesn't have values, because it is an existing type you can still define traits on it. You could plausibly define any existing trait on it, or none at all, because it's logically consistent the either way: because the values don't exist, you can't actually call the trait methods so any trivial implementation (such as method that panics / throws and exception when called) will do. You won't get logical contradiction because you can't call it.
But then you get the difficulty of, which actual traits should that type implement? That's a hard decision to make, and it's the reason why Rust doesn't yet have a first class bottom type.
- lmm 6y ago> But then you get the difficulty of, which actual traits should that type implement? That's a hard decision to make No it's not? It implements every trait. That's the whole point of it being a bottom type.
- GolDDranks 6y agoThen you might get logical inconsistencies with traits that are designed to be mutually exclusive and are used in type-level programming.
- Joker_vD 6y agoThere's a whole 6.1 section in "Java and Scala Type Systems are Unsound" [0] dedicated to "nonsense types". [0] https://raw.githubusercontent.com/namin/unsound/master/doc/unsound-oopsla16.pdf https://raw.githubusercontent.com/namin/unsound/master/doc/u...
- Sharlin 6y agoIt gets problematic with associated types/constants/functions. For instance, given the following Rust trait: trait Foo { type AssocType; const ASSOC_CONST: i32; fn assoc_fn() -> i32; } what would the following evaluate to if `!` automatically implemented `Foo`? <! as Foo>::AssocType <! as Foo>::ASSOC_CONST <! as Foo>::assoc_fn() These can all occur in reachable code because they do not depend on `!` having inhabitants.
- genneth 6y agoCompiler error if these are ever instantiated/need to be monomorphised? The problem is that this is very unrustic by having the error be very non local. But maybe this is unavoidable once you have sufficiently complex const fn anyway...
- DougBTX 6y agoThere's a principle in Rust that only the function signature is needed to know if a function call is valid, without any need to look at the function body. It makes things much easier to think about: at a minimum, if you change the body of a function it doesn't affect whether other code compiles or not. It's also one of the big differences between generic parameters (all in the function signature) and template metaprogramming (templates can do duck typing).
- creata 6y agoThe obvious answer to that is then "any trait consisting only of methods which take a Self parameter". But given (a) how confusing Rust's orphan rules already are to the average programmer, and (b) how easy it is to implement your own Never type if needed, I think it's best to just have Never implement the main built-in traits (Copy, Clone, etc.) and leave it at that.
- GolDDranks 6y agoThinking about this problem again kinda nerd-sniped me, so sharing a brain-dump here. The first one: any type that fulfills the trait bounds set by the definer of the trait, makes sense here. If we want all associated types of ! uniformly to map a single type, the only possible choice would be !, because we are trying to define it here so that it fullfills all possible trait bounds. Another choice would be the "top" type: a generic T (module the trait bounds). Here, the type inference would give up, as it would have no information about the type, and user of the trait would need to fill in whatever type they desire in this specific type-level expression. Any other type would be kinda arbitrary, and not maximally expressive in either way. That the user would need to select a type, is an interesting choice in the sense, that <! as Foo> would not be a valid expression in that case. It would need to be something like <! as Foo<AssocType=MyType>>, which is intriguing to me: the user of the trait would become basically an implementor of it, and passing ! to anywhere with a bounded generic type T: Foo, would instantiate not only the type T with !, but also the trait Foo with <AssocType=MyType>! The second and the third ones: these are even tougher, as they are expressions that produce values. Rust doesn't guarantee that someone who has a type can produce a value of that type just by the virtue of having the type. But mimicking the first case, one could either imagine that they are panicking expressions of type !, which, confortably is a subtype any type, and thus any associated expression! But panicking in such cases sounds a bit awful from resiliency perspective. Another one is that again, like with the second case, the user would instantiate the trait Foo itself with <ASSOC_CONST=CONST> or <assoc_fn=func>. This would be also maximally flexible. However, instantiating traits by setting their "parameters" is not something that Rust currently has. To be able to do that in generic code, one must also have a way to propagate such instantiated traits (e.g. from output to input). One could maybe do this without introducing "trait variables" or anything like that, by just tracking where type variables go. Still, it's a whole new consept in the already complex and hard to understand trait system.
- darksaints 6y agoNot even sure how it is even possible to implement every trait. Wouldn't it be the opposite? It implements no traits?
- GolDDranks 6y agoOne could argue that because the bottom type is a subtype of all other types, it must to implement all the traits that any other type implements. Traits are similar to interfaces in this regard, and it's pretty clear that subtypes must implement the same interfaces their supertypes do. If not, the values of the subtype wouldn't conform to the Liskov substitution principle. The special case of the bottom type, however, is made possible by the fact that it doesn't have values, so Liskov substitution principle is irrelevant in practice. It's pretty clear, however, that 1) it's kind of weird to do a full reversal on how subtypes work in one case (implements all the traits of the supertypes) versus others (doesn't implement any trait). 2) you want your types to be maximally flexible, so implementing more traits is good, because it allows one to use the type in more situations.
- lmm 6y agoThe whole point of a bottom type is that it's impossible to actually have an instance of it. It implements everything in a principle-of-explosion sort of way.