Description
So @scalexm pointed me at this curious example:
trait Animal<X>: 'static { }
fn foo<Y, X>() where Y: Animal<X> + ?Sized {
// `Y` implements `Animal<X>` so `Y` is 'static.
baz::<Y>()
}
fn bar<'a>(arg: &'a i32) {
foo::<Animal<&'a i32>, &'a i32>()
// If you uncomment this line, you will get an error.
//baz::<Animal<&'a i32>>()
}
fn baz<T: 'static + ?Sized>() {
}
fn main() {
let a = 5;
bar(&a);
}
There's a lot going on here, but the key point is that, in the call to foo::<Animal<&'a i32>, &'a i32>
, we assume that Animal<&'a i32>: Animal<&'a i32>
. This is reasonable, since it's a trait object type -- however, it is a malformed trait object type, and hence one where no impl could ever exist (i.e., we could never make an instance of this type). Interestingly, we are not smart enough to extend this to the case where the 'static
bound is applied directly (hence the commented out call to baz::<Animal<&'a i32>>
.
At the very least, this is inconsistent behavior, but I feel like there is an unsoundness lurking here. What makes me nervous is that we say that a projection type <P0 as Trait<P1...Pn>>::Foo
outlives 'a
if Pi: 'a
for all i
. I am worried that you could use an impl like this:
trait Projector { type Foo; }
impl<X> Projector for Animal<X> {
type Foo = X;
}
to prove then that <Animal<&'a i32> as Projector>::Foo
outlives 'static
(say). I have not yet quite managed to weaponize this, but I got pretty close -- that's a gist in which we invoke a function is_static<U: 'static>(u: U)
where U
will be of type &'a i32
when monomophized. If rustc were as smart as chalk, I suspect we could easily craft a transmute of some kind to &'static i32
. There may be a way to do it within current rustc too.
I'm not sure what's the best fix here. I suspect we should require that trait object types meet the WF requirements declared on the trait; and yet, I think the reason we did not is because we don't know the Self
type, which means we can't fully validate that. =) This also feels a mite inconsistent with implied bounds. (Perhaps most likely is that we could tweak the WF rules for trait objects to specifically target lifetime bounds, but I have to think about that, feels .. hacky?)
We could tweak the notion of what type parameters are constrained -- or at least which may appear in a projection. e.g., we could disallow that impl of Projector
for Animal<X>
, because X
would not be considered "sufficiently" constrained to appear in type Foo = ...
. Backwards incompatible, obviously, and feels unfortunate.
This causes actual unsoundness, as discovered by @scalexm in #44454 (comment);
use std::any::Any;
trait Animal<X>: 'static { }
trait Projector {
type Foo;
}
impl<X> Projector for dyn Animal<X> {
type Foo = X;
}
fn make_static<'a, T>(t: &'a T) -> &'static T {
let x: <dyn Animal<&'a T> as Projector>::Foo = t;
let any = generic::<dyn Animal<&'a T>, &'a T>(x);
any.downcast_ref::<&'static T>().unwrap()
}
fn generic<T: Projector + Animal<U> + ?Sized, U>(x: <T as Projector>::Foo)
-> Box<dyn Any>
{
make_static_any(x)
}
fn make_static_any<U: 'static>(u: U) -> Box<dyn Any> {
Box::new(u)
}
fn main() {
let a = make_static(&"salut".to_string());
println!("{}", *a);
}
Activity
nikomatsakis commentedon Sep 9, 2017
Actually, thinking about this more, the problem is maybe not quite how I was thinking of it. That is, it seems like the problem lies in the definition of object safety. That is, if you imagine that you had to write the impl for
Animal<X>
by hand, it would look like:and this would fail the well-formedness checks because
Animal<X>: 'static
does not hold, at least not without relying on the impl we are writing =).The goal of the object safety rules are to ensure such an impl could be written.
Maybe the rules of object safety have to be more conditional -- i.e.,
Animal<X>
is object safe ifX: 'static
.scalexm commentedon Sep 9, 2017
Here is an actual unsoundness, using
std::any::Any
:https://play.rust-lang.org/?gist=1385ed4e051354b305ea2d2413f8724f&version=stable
[-]*possible* unsoundness relating to WF requirements on trait types[/-][+]*possible* unsoundness relating to WF requirements on trait object types[/+][-]*possible* unsoundness relating to WF requirements on trait object types[/-][+]unsoundness relating to WF requirements on trait object types[/+]arielb1 commentedon Sep 10, 2017
This looks similar to #27675 - in both cases we have a trait object that is not WF.
I think a reasonable way to solve this would be to have a trait object predicate depend on a
WF(Self)
predicate, so we basically have the rulearielb1 commentedon Sep 11, 2017
The "this part is new" part is actually more subtle than I realized.
Object types (aka
dyn Trait<'λ₀...'λₙ, τ₀...τₙ>
, to avoid confusion) are actually a funny way of writingAnd the idea is that if the trait is object-safe, we can write a "compiler impl"
If we want to write such an impl, we need the where-clauses on the trait (both associated types and others) to hold. For supertraits, this follows by obvious induction. For other constraints, this is something that won't hold by itself.
Therefore, we probably need to add these other predicates as conditions on the impl, and have
nikomatsakis commentedon Sep 11, 2017
I agree that it is the same as #27675. As we said on IRC, I think that the current fix is roughly as I described here -- in particular, when we check that
dyn Trait<P...>: Trait<P...>
, we need to also check that the where-clauses declared onTrait
hold fordyn Trait<P...>
. This subsumes some of the object safety checks (e.g. the search forwhere Self: Sized
).bstrie commentedon Sep 14, 2017
Shall we close this as a dupe, then?
&T
sometimes incorrectly allowed #3889946 remaining items