-
Notifications
You must be signed in to change notification settings - Fork 182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
implied bounds for trait objects #203
Comments
While reading this, I found myself wondering about RFC 2027, which aims to tweak the rules around |
@nikomatsakis I guess for that RFC, we could just move the Edit: wouldn't work because we require |
@scalexm and I discussed RFC 2027 on Zulip here. We came to the conclusion (roughly around here) that it should be compatible, but we have to be a bit careful with the setup. |
This implements the impl rules from rust-lang#203, as far as I understood them. It doesn't do the well-formedness or object safety rules.
This implements the impl rules from rust-lang#203, as far as I understood them. It doesn't do the well-formedness or object safety rules.
This implements the impl rules from rust-lang#203, as far as I understood them. It doesn't do the well-formedness or object safety rules.
This implements the impl rules from rust-lang#203, as far as I understood them. It doesn't do the well-formedness or object safety rules.
This implements the impl rules from rust-lang#203, as far as I understood them. It doesn't do the well-formedness or object safety rules.
Generate `Normalize` clauses for dyn and opaque types When a trait `Trait` has a transitive supertrait defined like `trait SuperTrait: AnotherTrait<Assoc = Ty>`, we've been generating `impl AnotherTrait` for `dyn Trait` and `opaque type T: Trait` but without accounting for the associated type bound. This patch adds `Normalize` clauses that correspond to such bounds. For example, given the following definition ```rust trait SuperTrait where WC { type Assoc where AssocWC; } trait Trait: SuperTrait<Assoc = Ty> {} ``` we generate the following clauses ```rust // for dyn Trait Implemented(dyn Trait: SuperTrait) :- WC Normalize(<dyn Trait as SuperTrait>::Assoc -> Ty) :- WC, AssocWC // this patch adds this // for placeholder !T for opaque type T: Trait Implemented(!T: SuperTrait) :- WC Normalize(<!T as SuperTrait>::Assoc -> Ty) :- WC, AssocWC // this patch adds this ``` I think this doesn't contradict #203 as "we may legitimately assume that all things talking directly about `?Self` are true", but I'm not really sure if this is the right direction. One caveat is that this patch exacerbates "wastefulness" as in [this comment](https://github.com/rust-lang/chalk/blob/1b32e5d9286ca48c50683177419b6f1512a49be5/chalk-solve/src/clauses.rs#L446-L449) as it adds the `Normalize` clauses even when we're trying to prove other kinds of goals. Fixes #777
We need to design rules for the well-formedness of trait objects that address the soundness issue rust-lang/rust#44454 and that are future-proof under implied bounds.
In what follows, we'll write
dyn Trait
to actually denotedyn Trait + 'a
for a placeholder lifetime'a
, the rules will work regardless of'a
.Trait objects in rustc
Say we have the following trait:
We will assume that this trait has no methods -- methods only change a bit the definition of "object-safety". Then we say that the trait is object-safe if:
Self
does not appear as a non-self type parameter in the where clausesWC1
, ...,WCn
or in the where clauses of any super-trait ofFoo<T1...Tk>
Sized
is not a transitive super-trait ofFoo<T1...Tk>
Then
dyn Foo<...>
is considered well-formed ifFoo
is object-safe. Example of object-safe traits:Example of non-object safe traits:
Then, whenever a trait
Foo<T1...Tk>
is object-safe, for each super-traitSuperTrait<...>
ofFoo<T1...Tk>
(includingFoo<T1...Tk>
itself), the compiler unconditionally generates the following impl:Why the previous approach is unsound
The unsoundness comes from the generated impls above. For example,
dyn Foo<&'a i32>
in this example is a well-formed trait object:and we have that
dyn Foo<&'a i32>: Foo<&'a i32>
even ifdyn Foo<&'a i32>
does not outlive'static
.Another example which may seem weird:
Note that with this
X: Copy
example, you cannot do anything useful because Rust does not support fully general implied bounds for now, however we must keep this example in our head if we want do design something which works with implieds bounds.However, because Rust does support implied bounds for outlive requirements, here is what you can do with the first example:
(playground)
This works because in
only_foo
, we bound overT: Foo<X>
, so we get an implied boundT: 'static
as per the definition oftrait Foo<X>
. And because outlive requirements are syntactical, ifT: 'static
, then rustc deduces that<T as Projector>::Item
must be'static
.Starting from there, we may confuse the compiler enough to create memory unsoundness (see some examples in rust-lang/rust#44454).
Proposed rules for a prototype in chalk
The problem with the previous approach comes from the fact that the generated impls are unconditional, and that we can name types that should obviously not be well-formed. Basically, a
dyn Foo<X>
is just an existential type?Self
such that?Self: Foo<X>
holds. So we may legitimately assume that all things talking directly about?Self
are true (e.g. that?Self
implements all super-traits oftrait Foo<X>
). However, because we have lost the information of where exactly the type parameterX
appears in?Self
(note that it may not appear at all), we cannot assume anything neither aboutX
nor about any outlive requirements on?Self
.Here are a set of rules which I think solve the problem. First, we keep the definition of object-safety, and we'll say that the special domain goal
ObjectSafe(Trait)
holds iftrait Trait<T1...Tk>
is object-safe. We may either encode the rules forObjectSafe(Trait)
in the logic or pre-compute it for each trait, I believe either method works.Now, suppose we have the following trait:
and let
SuperTrait<...>
be any transitive super-trait ofFoo<T1...Tk>
. IfSuperTrait<...>
comes from the following definition:then we generate the following impl:
where
SuperWC1[...]
just means that we have substitutedSuperWC1
from the trait definition with the right type parameters. Note again that in the set of the super-traits ofFoo<T1...Tk>
, we haveFoo<T1...Tk>
itself.Finally, we have the following rule for the well-formedness of
dyn Foo<X1...Xk>
:I believe that this solution can be implemented in rustc today.
Note that, in the rules described earlier, we added where clauses from the super traits on every generated impl only so that the impl is well-formed even without implied bounds, I am not sure they are strictly needed.
Examples
In the above rules, the fact that we generate an impl of
SuperTrait<...>
fordyn Foo<T1...Tk>
encodes the previous claim:while the fact that we are retaining where clauses from
SuperTrait<...>
on the generated impl encodes the other claim:Let's walk through some examples to make that clearer.
In the above, the well-formedness of
dyn Foo<X>
only holds ifX: 'static
or more generally if you can prove thatdyn Foo<X>: 'static
.In the above,
dyn Foo<i32>
is WF but notdyn Foo<String>
.Implied bounds
Because of the well-formedness rule, we naturally have the following implied bound:
This permits writing things like:
With this reverse rule and implied bounds, the where clauses from super-traits on the generated impls are definitely not needed.
A curious example
Today in Rust, you can have the following trait:
The trait
Foo
is object-safe anddyn Foo
is well-formed. However, the following code is not accepted:However, we may add the following (manually written) impl:
In which case we can now call
foo::<dyn Foo>()
.With my proposal, what would happen is that in the absence of
impl Bar for Box<dyn Foo>
, thendyn Foo
would not be considered well-formed, becauseBox<Self>: Bar
is not really a super-trait. Withimpl Bar for Box<dyn Foo>
, then everything works again.Question is: should we try to extend the generated impls to all bounds, so that we don't need to write the
impl Bar for Box<dyn Foo>
impl? Note that generating the impls for all the transitive super-traits must be done outside of the logic, but is easy. However, generating impls for all bounds would require some kind of fixed-point algorithm. Moreover, this may be even harder for traits with bounds on associated types, like in:Multi-trait objects
Note that this proposal naturally extends to multi-trait objects. For example, you'd have:
while the generated impls would come from super-traits of both
Foo
andBar
.The text was updated successfully, but these errors were encountered: