4 ms·
It gets problematic with associated types/constants/functions. For instance, given the following Rust trait: trait Foo { type AssocType; const ASSOC_
by Sharlin 6y ago
It 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.