You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Replace current quasi-subtyping relationship for object types with coercions #18737
Currently the type checker considers A+Send to be a subtype of A, but not for the purposes of inference. Part of the DST / coercion plan was to generalize this into a coercion relationship so that it can accommodate arbitrary traits. We should do this before 1.0 as it will cause various minor bits of breakage. Among other things, method resolution will have to be adapted to accommodate this change.
P-backcompat-lang, 1.0, but its possible that this is lesser priority than other P-backcompat-lang issues (i.e. we may be able to hold off on fixing this until the beta period).
An example that this may fix (or maybe a good smoke test)
pubtraitAny:'static{}pubstructBox2<T: ?Sized>(Box<T>);implBox2<Any>{pubfnfoo(self){}}fnt2(t:Box2<Any + Send>){
t.foo();// type `Box2<Any + Send>` does not implement any method in scope named `foo`}
This is largely in relation to the BoxAny trait we currently have.
Hmm, I encountered a bit of a footgun. It's kind of related to all of the method dispatch above actually (and suggests, in fact, that more general fat pointer coercion would make the Any API harder to use). In particular, what would you expect this code to do:
Presuming that x is, in fact, a Box<S>, I would expect this to return true. But it returns false. The reason is that Any expects a &Any. It is supplied a &Box<Any+Send>. Rather than deref coercions kicking in, as I expected, we get a trait coercion where Box<Any+Send> is coerced to the trait object Any, and thus the type test fails because S != Box<Any+Send>. The code works fine if you write &*x, in particular. Note that if we did the full fat-pointer-based method dispatch I described above, the same phenomenon would arise.
This problem is somewhat specific to the Any API, because it is implemented basically for all T, but it suggests that free-fns can also be easily misused. It'd be nice to have an API form that is downright hard to misuse. I'm not sure what's the best fix here.
If the Any+Send case is generalized to Foo+X, does this mean that methods defined via impl Foo will not be available on Box<Foo+X> (regardless of X)?
Hmm, I encountered a bit of a footgun.
Hm that is... unfortunate! I do agree though that it is Any-specific, however. I recall running across this footgun in the past (both myself and via questions on IRC), so I wouldn't be too too worried about it. I think it's fine to move to free functions or require UFCS (perhaps favoring UFCS as the methods are #[stable] currently)
How would moving to UFCS solve it? Isn't the problem that with <&Any as Any>::is::<S>(&ex) rustc coerces ex to Any by an autoref (to &Box<Any>) followed by an unsize rather than by an autoderef to Box<Any> followed by an autoref?
If the Any+Send case is generalized to Foo+X, does this mean that methods defined via impl Foo will not be available on Box<Foo+X> (regardless of X)?
Yes.
I think it's fine to move to free functions or require UFCS (perhaps favoring UFCS as the methods are #[stable] currently)
I think I favor free fns only because I find it somewhat more ergonomic. For example, you can do use any::is and then write is::<T>(x) vs Any::is(x). Similarly, <Box<Any>>::downcast() is fairly annoying.
Hm that is... unfortunate! I do agree though that it is Any-specific, however. I recall running across this footgun in the past (both myself and via questions on IRC), so I wouldn't be too too worried about it.
I don't see any clear sol'n -- but I did find forward porting some tests to be fairly annoying due to this. This was mostly a problem in various "torture tests" that seemed to have particularly confusing type casts going on though.
How would moving to UFCS solve it? Isn't the problem that with <&Any as Any>::is::<S>(&ex) rustc coerces ex to Any by an autoref (to &Box<Any>) followed by an unsize rather than by an autoderef to Box<Any> followed by an autoref?
Sorry, I'm not sure what comment of mine you are referring to, but:
UFCS solves the problem where I supply a &(Any+Send) and a &Any is expected. This is because we can't make an Any object from an Any+Send object except by upcasting (you can only make an object from a sized type).
UFCS does not solve the footgun I mentioned where you supply an &Box<Any+Send> and an &Any is expected, because Box<Any+Send> can be "objectified" to Any.
@nikomatsakis sorry I didn't get to this sooner. Your approach sounds reasonable, but the footguns surrounding the use of Any and other traits which are implemented for most/all T make me less enthused about this. I can work around anything in a downstream library, but it would make me a lot more apprehensive about using unsafe-any, since the chance of something going horribly wrong is much higher.
@reem yes, I was wondering about what's the best solution here. Definitely I found working with Any to be... precarious when I was forward-porting tests. One possibility is of course a custom lint to watch out for things like coercing a &Box<Any+Send> into a &Any as an argument of any::is and friends, but that won't help for unsafe_any (and feels...very tailored!).
Another possibility is that I could keep the simple hack that dropped all builtin bounds from method dispatch (i.e., step from Trait+Send to Trait). But that feels unfortunate because it is involving a lot of concepts I would like to go away. It's not a very general mechanism so I'd hate to be relying on it forever.
Well, something else just occurred to me. One other simple fix here might be to implement the relevant methods for Any on Any+Send as well. Kind of a hack, but at least we'd only have to get the code that does the upcasting correct once, and this is a very common case due to the thread error propagation APIs.
I implemented the approach of adding extra impls for Any. This causes all existing code in libstd to work and seems to address the footgun problem, while still leaving us room in the future to expand method dispatch to handle more such cases (this basically just builds in one particular upcast).
Activity
nikomatsakis commentedon Nov 7, 2014
Nominating for P-backcompat-lang, 1.0.
pnkfelix commentedon Nov 13, 2014
P-backcompat-lang, 1.0, but its possible that this is lesser priority than other P-backcompat-lang issues (i.e. we may be able to hold off on fixing this until the beta period).
nikomatsakis commentedon Nov 13, 2014
If push came to shove, we could live with this behavior.
aturon commentedon Jan 8, 2015
Nominating. It sounds like this should not be on the -beta milestone, but we should discuss.
pnkfelix commentedon Jan 8, 2015
Assigning 1.0-beta milestone based on potential for breaking lots of code according to nrc.
alexcrichton commentedon Jan 14, 2015
An example that this may fix (or maybe a good smoke test)
This is largely in relation to the
BoxAnytrait we currently have.8 remaining items
nikomatsakis commentedon Mar 12, 2015
I also posted the above comment to discuss: http://internals.rust-lang.org/t/impact-of-fixing-subtyping-for-object-types-18737/1709
nikomatsakis commentedon Mar 12, 2015
Hmm, I encountered a bit of a footgun. It's kind of related to all of the method dispatch above actually (and suggests, in fact, that more general fat pointer coercion would make the
AnyAPI harder to use). In particular, what would you expect this code to do:Presuming that
xis, in fact, aBox<S>, I would expect this to return true. But it returns false. The reason is thatAnyexpects a&Any. It is supplied a&Box<Any+Send>. Rather than deref coercions kicking in, as I expected, we get a trait coercion whereBox<Any+Send>is coerced to the trait objectAny, and thus the type test fails becauseS != Box<Any+Send>. The code works fine if you write&*x, in particular. Note that if we did the full fat-pointer-based method dispatch I described above, the same phenomenon would arise.This problem is somewhat specific to the
AnyAPI, because it is implemented basically for allT, but it suggests that free-fns can also be easily misused. It'd be nice to have an API form that is downright hard to misuse. I'm not sure what's the best fix here.alexcrichton commentedon Mar 12, 2015
If the
Any+Sendcase is generalized toFoo+X, does this mean that methods defined viaimpl Foowill not be available onBox<Foo+X>(regardless ofX)?Hm that is... unfortunate! I do agree though that it is
Any-specific, however. I recall running across this footgun in the past (both myself and via questions on IRC), so I wouldn't be too too worried about it. I think it's fine to move to free functions or require UFCS (perhaps favoring UFCS as the methods are#[stable]currently)arielb1 commentedon Mar 12, 2015
@nikomatsakis
How would moving to UFCS solve it? Isn't the problem that with
<&Any as Any>::is::<S>(&ex)rustc coercesextoAnyby an autoref (to&Box<Any>) followed by an unsize rather than by an autoderef toBox<Any>followed by an autoref?nikomatsakis commentedon Mar 16, 2015
@alexcrichton
Yes.
I think I favor free fns only because I find it somewhat more ergonomic. For example, you can do
use any::isand then writeis::<T>(x)vsAny::is(x). Similarly,<Box<Any>>::downcast()is fairly annoying.I don't see any clear sol'n -- but I did find forward porting some tests to be fairly annoying due to this. This was mostly a problem in various "torture tests" that seemed to have particularly confusing type casts going on though.
nikomatsakis commentedon Mar 16, 2015
@arielb1
Sorry, I'm not sure what comment of mine you are referring to, but:
&(Any+Send)and a&Anyis expected. This is because we can't make anAnyobject from anAny+Sendobject except by upcasting (you can only make an object from a sized type).&Box<Any+Send>and an&Anyis expected, becauseBox<Any+Send>can be "objectified" toAny.reem commentedon Mar 16, 2015
@nikomatsakis sorry I didn't get to this sooner. Your approach sounds reasonable, but the footguns surrounding the use of
Anyand other traits which are implemented for most/all T make me less enthused about this. I can work around anything in a downstream library, but it would make me a lot more apprehensive about using unsafe-any, since the chance of something going horribly wrong is much higher.nikomatsakis commentedon Mar 17, 2015
@reem yes, I was wondering about what's the best solution here. Definitely I found working with
Anyto be... precarious when I was forward-porting tests. One possibility is of course a custom lint to watch out for things like coercing a&Box<Any+Send>into a&Anyas an argument ofany::isand friends, but that won't help forunsafe_any(and feels...very tailored!).Another possibility is that I could keep the simple hack that dropped all builtin bounds from method dispatch (i.e., step from
Trait+SendtoTrait). But that feels unfortunate because it is involving a lot of concepts I would like to go away. It's not a very general mechanism so I'd hate to be relying on it forever.nikomatsakis commentedon Mar 17, 2015
Well, something else just occurred to me. One other simple fix here might be to implement the relevant methods for
AnyonAny+Sendas well. Kind of a hack, but at least we'd only have to get the code that does the upcasting correct once, and this is a very common case due to the thread error propagation APIs.Remove subtyping for object types and replace with an *upcast* coercion.
nikomatsakis commentedon Mar 17, 2015
I implemented the approach of adding extra impls for
Any. This causes all existing code in libstd to work and seems to address the footgun problem, while still leaving us room in the future to expand method dispatch to handle more such cases (this basically just builds in one particular upcast).Auto merge of #23423 - nikomatsakis:issue-18737-trait-subtyping, r=nrc
Merge pull request rust-lang#18737 from Veykril/push-rslstwynmzpx