Skip to content
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

Disjointness based on associated types. #1672

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions 0000-disjoint_associated_types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
- Feature Name: disjoint_associated_types
- Start Date: 2016-07-10
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)

# Summary
[summary]: #summary

During coherence checking, when determining if the receivers of two impls are
disjoint, treat bounds with disjoint associated types as mutually exclusive
bounds.

# Motivation
[motivation]: #motivation

Consider this set of impls:

```rust
impl<T> Foo for T where T: Iterator<Item=u64> { }
impl<T> Foo for T where T: Iterator<Item=i64> { }
```

Both of these are "blanket impls" - they implement the trait `Foo` for an open
set of types - any type which implements `Iterator`, with the item `u64` in
the first, or the item `i64` in the second.

Blanket impls are a tricky beast, because often blanket impls will overlap with
other impls of the same trait, because the type being implemented for also
falls into the blanket impl (or could).

However, these two blanket impls, intuitively, do not overlap. Because you can
only implement `Iterator` once for each type, you cannot have a type which
implements `Iterator` with both of these types.

Another example of how this could be useful are the return types of functions.
Consider this code:

```rust
trait Applicator {
fn apply(&mut self, Event) -> io::Result<()>;
}

fn apply_it<T>(T) -> io::Result<()> where T: Applicator { ... }

impl<F> Applicator for F where F: Fn(Event) -> io::Result<()> { ... }

impl<F> Applicator for F where F: Fn(Event) -> () { ... }
```

The return type here is an associated type, a function cannot return both
`io::Result<()>` and `()`. I had code a lot like this in a library I was
writing; the idea was that quick and dirty stateless implementations of the
analog trait to `Applicator` could be written as closures, returning either
an `io::Result` or unit. However, these impls are regarded as overlapping by
rustc today.

# Detailed design
[design]: #detailed-design

When considering whether two type variables are disjoint, these (informal)
rules prove that they are disjoint:

1. If they are both concrete types, and they are not the same type (this rule
already exists).
2. If they are both bound by the same trait, and both specify the same
associated type for that trait, and the types they specify are disjoint.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps add a rule that instantiations of generic types with disjoint parameters are disjoint. i.e.

impl<T: Trait1<Item=X>> Trait2 for Foo<T> { ... }
impl<T: Trait1<Item=Y>> Trait2 for Foo<T> { ... }

or even

impl<T: Trait1<Item=X>, U: Trait2<Item=Foo<T>>> Trait3 for U { ... }
impl<T: Trait1<Item=Y>, U: Trait2<Item=Foo<T>>> Trait3 for U { ... }

Copy link
Contributor Author

@withoutboats withoutboats Jul 11, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. We don't have good, consistent language about this, but when I wrote "two type variables" I meant any type variables (really I meant any type, whether an abstract variable or a concrete type, since the first rule only applies to concrete types), whether they be the receiver of the trait or not (I realize the summary is less broad than this, oops).

This is exactly what I had in mind when I said this rule was recursive.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any case where T is disjoint but Foo<T> is not disjoint today? If not it seems redundant to re-specify it here.

Copy link
Contributor Author

@withoutboats withoutboats Jul 11, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@comex was talking about the transitivity of the disjunction rule, but perhaps we should note that if T is disjoint with U and X is a type constructor of the kind type -> type, X<T> is disjoint with X<U>. No reason this couldn't be a roundup to establish and document some of the basic disjunction rules.

EDIT: Actually, this may impact how that rule is implemented. Currently I think all disjunction is based on inequality of concrete types (e.g. we can just see that Foo<Bar<Baz<i32>> is not Foo<Bar<Baz<bool>>), but now that rule also needs to take into account type variables that are bound exclusively.


Additional rules could be added in separate RFCs, such as rules based on a
syntax for mutual exclusion.

Note that the second rule is recursive.


# How will we teach this?
[teach]: #teach

This will need to be documented in the reference or some other detailed
document, along with the general description of how Rust coherence works.
Otherwise, this doesn't particularly need to be called out separate from other
aspects of the coherence system.

# Drawbacks
[drawbacks]: #drawbacks

This adds more rules to coherence, making it more complicated. However, they
are intuitive rules, which align with the expected behavior of coherence, so
hopefully they will not add to the learning burden.

# Alternatives
[alternatives]: #alternatives

Explicit mutual exclusion of bounds ("negative bound syntax") could provide
some, but not all, of the benefits of this feature.

We could always do nothing and leave coherence as it is.

# Unresolved questions
[unresolved]: #unresolved-questions

None.