Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upMutually exclusive traits #1148
Conversation
added some commits
Jun 3, 2015
alexcrichton
added
the
T-lang
label
Jun 3, 2015
llogiq
reviewed
Jun 3, 2015
| fn nourish(&self); | ||
| } | ||
| pub trait Poisonous: !Edible { |
This comment has been minimized.
This comment has been minimized.
llogiq
Jun 3, 2015
Contributor
Do we require that both traits declare themselves to be negatively bounded by the other? It certainly improves readability, especially in larger source files. If this is the case, the compiler should generate a warning if Edible : !Poisonous, but Poisonous is not bounded by !Edible.
This comment has been minimized.
This comment has been minimized.
withoutboats
Jun 3, 2015
Author
Contributor
Edible: !Poisonous implies Poisonous: !Edible, so there wouldn't be a coherence violation if one denotation was left off. I agree that it is better readability for both to be denoted, and a lint would be good.
This comment has been minimized.
This comment has been minimized.
llogiq
reviewed
Jun 3, 2015
|
|
||
| ## Implementing ?Trait | ||
|
|
||
| `?Trait` can only be implemented for types for which a default impl has been explicitly defined (e.g. `Send` and `Sync`). Explicit default impls of `?Trait` are not allowed. `?Trait` is implemented by default anyway, and it would not make sense to implement it except in the cases where an explicit default impl exists. As a rule, the syntax `?Trait` will be very uncommon. |
This comment has been minimized.
This comment has been minimized.
llogiq
Jun 3, 2015
Contributor
This is a bit unclear to me. So ?Send is implemented for which types? What are the semantics of implementing ?Trait? What about bounding by ?Trait? I'd like the text to either forbid it or specify what it actually means.
This comment has been minimized.
This comment has been minimized.
withoutboats
Jun 3, 2015
Author
Contributor
A type which implements ?Send would not meet the bound T: Send or T: !Send. The only reason to implement ?Send over !Send is if you believe you might one day be able to mark the type Send and don't want that to be backwards incompatible (I don't know if any types that would want this exist; I doubt that they do).
Bounding by ?Trait is not possible and not sensible; all type parameters are bound ?Trait for all traits unless bound otherwise, and that bound means nothing (that is, a type parameter T may or may not implement any particular trait unless it has a bound which declares otherwise).
This comment has been minimized.
This comment has been minimized.
llogiq
Jun 3, 2015
Contributor
So impl ?Send just means that no one should rely on the type being !Send now or in the future. That makes sense. Would you add a sentence to that effect to clarify this paragraph?
llogiq
reviewed
Jun 3, 2015
|
|
||
| ## Syntactic sugar: Implicit `!Trait` inference | ||
|
|
||
| If a type `T` implements a trait `Foo` which is bounded `!Bar`, an implementation of `!Bar` is inferred for `T` (unless `T` explicitly implements `!Bar`, of course). This avoids boilerplate negative impls which are inferrable from other impls for the type. |
This comment has been minimized.
This comment has been minimized.
llogiq
Jun 3, 2015
Contributor
This is a very cool feature, but looks a bit like spooky action from a distance. We should make sure that error messages (e.g. if someone tries to impl Bar for T) reflect the actual reason.
This comment has been minimized.
This comment has been minimized.
withoutboats
Jun 3, 2015
Author
Contributor
I agree with this. If someone attempts to implement as an example, Float and Integer for their type, the error they should get should be to the effect that Float contradicts Integer, not something about !Float or !Integer, which they have not directly used.
This comment has been minimized.
This comment has been minimized.
jnicklas
Jun 3, 2015
I think I dislike this feature, and I preferred it being left out in the original draft. With a positive trait bound on a trait I have to manually implement it, I can't see why that would be different with a negative trait bound.
I would expect/prefer an error like:
test.rs:7:1: 7:23 error: the trait `!Bar` is not implemented for the type `SomeType` [E0277]
test.rs:7 impl Foo for SomeType {}
^~~~~~~~~~~~~~~~~~~~~~
This comment has been minimized.
This comment has been minimized.
llogiq
Jun 3, 2015
Contributor
Thinking about it a bit more, @jnicklas proposal certainly meshes well with the "make code semantically complete locally" philosophy of Rust (which is also why we only have fn-local type inference). Someone reading SomeType's code or using SomeType somewhere else will probably not have in mind that Foo : !Bar.
Requiring that SomeType : !Bar or impl !Bar for SomeType (which one btw.?) is a small price to pay for that improved readability.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
withoutboats
Jun 3, 2015
Author
Contributor
I chose to propose that sugar because it seemed like there wasn't enough in the pre-RFC for folks to disagree about. :-) In seriousness, I do think this is good sugar.
It's not actually true that you have to manually implement all positive traits which are bounds, because some are implemented by default through default impls. Default impls currently can only be provided for marker traits, and of the four non-built-in traits in std::marker (Send, Sync, Reflect, and Unsize), three are implemented by default. Negative traits are all marker traits, and while inference is a different mechanism from default impls, it is similar in principle.
If a type implements a trait, it cannot implement a trait which is mutually exclusive with that trait. Implementing that trait is a semantic declaration that this type does not implement traits that are mutually exclusive with the traits it does implement. Mutual exclusion implies that these traits are fundamentally disparate (e.g. Int and Float or Num and Collection), a client should not be expecting that a type which implements Int can be passed where a type is bound Float.
It seems much less of a leaping inference than the leap that Rust already makes in local contexts, which makes this code compile (and which cannot be removed).
pub trait Quux { }
pub struct Foo;
pub trait Bar { }
impl Quux for Foo { }
impl<T> Quux for T where T: Bar { }
This comment has been minimized.
This comment has been minimized.
llogiq
Jun 4, 2015
Contributor
If you want to stand by it, at least extend the Alternatives section to cover @jnicklas' proposal.
I'm currently leaning in favor of explicit stating the impl, because we can later remove the requirement in a backwards-compatible way. Introducing the requirement later would break code.
llogiq
reviewed
Jun 3, 2015
|
|
||
| The rules above all apply to a Rust system as a whole, composed of multiple crates associated as a directed acyclic graph. Within crates and modules, orphan rules allow silence to have a semantic expressions that is slightly different from these rules. Unfortunately, eliminating this warble would be backwards incompatible. | ||
|
|
||
| Specifically, when both a trait and a type are defined within a single crate, that type and trait have the relationship `T: !Trait` by default, rather than `?Trait`, only within that crate. This allows a certain degree of implicit negative reasoning which cannot be performed outside of that local context. It does not present a logical contradiction for this proposal. |
This comment has been minimized.
This comment has been minimized.
llogiq
Jun 3, 2015
Contributor
I'd like to have "unless T actually has an impl for Trait in addition to 'by default', to clarify the meaning.
This comment has been minimized.
This comment has been minimized.
|
Following @steveklabnik 's comment to my recent RFC submission, you should linebreak your Markdown. |
llogiq
reviewed
Jun 3, 2015
| | | impl ?Trait for T | | | | ||
| | Default impl | by default | impl Trait for .. | impl !Trait for .. | | ||
| | Bounds | by default | where T: Trait | where T: !Trait | | ||
| | | where T: ?Sized | by default for Sized | | |
This comment has been minimized.
This comment has been minimized.
llogiq
Jun 3, 2015
Contributor
I'd use code formatting for actual code and add more headers to clarify, like:
| ?Trait | Trait | !Trait | |
|---|---|---|---|
| Specific impl | by default | impl Trait for T |
impl !Trait for T |
| Explicit impl | impl ?Trait for T |
impl Trait for T |
impl !Trait for T |
| Default impl | by default | impl Trait for .. |
impl !Trait for .. |
| Bounds | by default | where T: Trait |
where T: !Trait |
| Explicit bound | where T: ?Sized |
by default for Sized |
I'm unsure about the "Explicit bound" header, though. Wouldn't that contradict your assertion that being bound by ?Trait makes no sense?
nrc
changed the title
Mutex traits
Mutually exclusive traits
Jun 3, 2015
This comment has been minimized.
This comment has been minimized.
|
Changed the title to avoid confusion with mutexes |
This comment has been minimized.
This comment has been minimized.
|
This seems like something that may have some prior art in a language like Haskell (or some variant), so it'd be good to have a look around to see if there's been research/experiments with this somewhere already. I'm just a little nervous that this may have unexpected interactions so it'd be good to get a broader view. |
bbatha
reviewed
Jun 3, 2015
|
|
||
| It may be difficult to grok the difference between `!Trait` and `?Trait`. The reason for this difference only becomes clear with an understanding of all the factors at play in the coherence system. Inferred `!Trait` impls and the rarity of `?Trait` impls should make this an unlikely corner of the trait system for a new user to accidentally happen upon, however. | ||
|
|
||
| The `impl !Trait for T` syntax overlaps with the syntax of existing negative impls for types with default impls, and has slightly greater semantic content under this RFC tahn before. For each existing negative impl, it will need to be determined whether that type should impl `!Trait` or `?Trait` (that is, whether or not the non-implementation is a guarantee). That said, this change is not backwards incompatible and will not cause any regressions, and existing negative impls are an unstable feature outside of std. |
This comment has been minimized.
This comment has been minimized.
bbatha
Jun 3, 2015
There's a spelling error here: "under this RFC tahn before" should be "under this RFC than before"
This comment has been minimized.
This comment has been minimized.
paholg
commented
Jun 4, 2015
|
This is very exciting and I hope it goes through. I would argue strongly in favor of the alternative proposal. If anyone is to use I feel I can best illustrate my point with a concrete example. Say that you want to create a linear algebra library as generically as possible, and you want to use trait Vector {}
struct Vec2<N> { x: N, y: N }
impl<N> Vector for Vec2<N> {}
// pairwise multiplication between vectors
impl<N: Mul<M>, M> Mul<Vec2<M>> for Vec2<N> {
type Output = Vec2<<N as Mul<M>>::Output>;
...
}
// scalar multiplication
impl<N: Mul<T>, T: !Vector> Mul<T> for Vec2<N> {
type Output = Vec2<<N as Mul<T>>::Output>;
...
}Under the alternative proposal, this is all that is needed and anyone can use your vectors with any type they wish. Under the main proposal, one would need to The workaround to this would be to include a second, essentially identical definition for scalar multiplication: impl<N: Mul<T>, T: ?Vector> Mul<T> for Vec2<N> {
type Output = Vec2<<N as Mul<T>>::Output>;
...
}This is clearly problematic. In fact, we could even forgo the first definition and only use the second, treating If creating trait |
This comment has been minimized.
This comment has been minimized.
|
@paholg Good point.
The RFC (line 126) proposes that |
This comment has been minimized.
This comment has been minimized.
paholg
commented
Jun 4, 2015
|
@llogiq I missed that. I would argue that it is just more confusing, though, and would cause unexpected bugs if a crate is ever split into two. Within a crate, it's trivial to specify whether a type implements |
This comment has been minimized.
This comment has been minimized.
|
@paholg This rule actually reflects the current coherence rules as far as I understand. So if it really is confusing, we should look into how it is documented and what errors may ensue and make sure that the documentation and error messages clear up any confusion. However, I see why you'd like to have the default |
This comment has been minimized.
This comment has been minimized.
Note that the alternative is accessible a la carte by declaring
|
This comment has been minimized.
This comment has been minimized.
This part of the RFC is a clarification of the current rule, which was arrived at by evolution. Originally, types were |
This comment has been minimized.
This comment has been minimized.
jnicklas
commented
Jun 4, 2015
|
In @paholg's example it'd also be possible to declare a mutually exclusive trait |
This comment has been minimized.
This comment has been minimized.
I looked around online & couldn't find anything like this in Haskell, so I asked in #haskell and it hasn't been considered. The only concern anyone in the channel raised had to do with orphan impls, which Haskell allows but Rust does not. I agree that this seems like something that could have surprising implications. I've tried hard to think of what they could be though and come up empty. |
This comment has been minimized.
This comment has been minimized.
|
Re: Local impls and warbling (using It actually is not necessary that local types and traits have the implicit relation EDIT: Actually, if you think of this 'warble' as just another kind of inference (if both type and trait are local, and two impls would overlap otherwise, infer that the type does not impl the trait), declaring the type fully |
This comment has been minimized.
This comment has been minimized.
Great, thanks for looking into it.
Yeah, I haven't thought of anything either, and I'm sure you've spent more time on it than me! |
This comment has been minimized.
This comment has been minimized.
Anachron
commented
Jun 4, 2015
|
I like this RFC, however, I have to admit that I'm afraid this could get too complicated (at least redundant).
Then you would have to declare non-meltability to all three types, but in real code this could become messy soon. (Imagine saying that Water is not Meltable, not Burnable, not Flameable and co, instead of saying what it does) I agree that both types should explicitly say their relationship and I also do not know a solution for this. |
This comment has been minimized.
This comment has been minimized.
|
@Anachron as long as |
This comment has been minimized.
This comment has been minimized.
Anachron
commented
Jun 5, 2015
|
@llogiq wouldn't that contradict the rule mentioned in #1148 (comment), that a missing declaration should at least trigger an info? |
This comment has been minimized.
This comment has been minimized.
|
That would be a lint, not a hard-and-fast rule. Lints can be deactivated using Btw. that comment is not yet part of the RFC if I read it correctly. @withoutboats Shall I send you a PR to that effect? |
This comment has been minimized.
This comment has been minimized.
|
So, for some time now, I've been working on a detailed response here. I'm getting closer. But working through some of my thoughts, I encountered a question that I don't quite know the answer to. Imagine that I have some type
Does To be honest, I'm not sure that thinking of |
This comment has been minimized.
This comment has been minimized.
|
Your interpretation is correct. Similarly, there's no reason to support explicit impls of |
This comment has been minimized.
This comment has been minimized.
|
So, I'm long overdue in adding some detailed feedback here, and I apologize for that! Let me start by saying that I really like this approach at a high level. I find the motivation compelling. In particular, I think there is a need to finish the story that RFC 1023 began. Currently, negative reasoning is permitted within a crate, but not outside. This was a pragmatic rule at the time but it doesn't seem like the best rule going forward. For one thing, it's not clear that crate authors are aware of the negative reasoning they are using: it's easy to make accidental assumptions. For another, there are plenty of times when you'd be willing to promise never to implement a trait: for example, That said, I have some concerns. I'm going to start with a comment that kind of resummarizes the RFC and points out some quibbles with the text itself. I've got another in the works with some deeper concerns around negative reasoning, specialization, etc (not all of which are specific to this RFC). Part of my purpose in resummarizing the RFC is to ensure I am understanding the intention, so please correct me if you think I have something wrong! Marker traits vs new kinds of predicatesI find some parts of the RFC a bit confusing. Sometimes it seems to use
but, under this RFC, we would have two kinds of predicates:
(For now, I've excluded Furthermore, for non-structural (that is, non-OIBIT) traits, we now have one kind of impl:
but we would now have two, a positive and a negative impl:
The idea would be that we can prove the predicate Structural traitsNow we come to structural traits. Here we currently have three sorts of impls:
With structural traits today, things are somewhat inverted. To prove that
In other words, to determine whether Anyway, this RFC changes the story in two ways. First, it makes a subtle change to the semantics of
This The second change is that this RFC adds a negative
My interpretation of the language from this section is that, whereas I described the positive sense as generating a defualt positive impl, this would generate a default negative impl:
In other words, to determine whether I don't really see the purpose for this form as presented. What I have seen in the thread doesn't seem correct. For example, @withoutboats presents
where Relationship to fundamentalI think what the As I wrote, some of the comments seem to suggest that this is what Anyway, it is great that this RFC provides a somewhat more structured way to think about fundamental. We should maybe consider whether want to devise another syntax for it. (Or maybe just continue with the current unstable attribute.) When considering a possible alternative syntax, also note that we support fundamental types today. A fundamental type On the topic of
|
This comment has been minimized.
This comment has been minimized.
|
Thanks! It's been a few months since I thought about this RFC, this is fun. Any lack of clarity is certainly my fault as author and not the fault of any reader. Especially anything to do with I think what this RFC, structural traits (thank you for this term which is much better than OIBITs!) and specialization are all circling around is this: the relationship between traits and types is well-described in terms of set theory. Traits and type parameters both refer to sets of types. We currently have
The goal of this RFC is essentially to introduce disjunction in a manner that is consistent with these constraints (mainly the first one). I'm going to digest your comments more, especially on |
This comment has been minimized.
This comment has been minimized.
Another way to look at this is in terms of 'intuitionistic' vs classical logic. Basically, it is not the case that either
If the concern is primarily about the syntax, I certainly agree. The attribute was chosen because it the a simple and low-cost way to introduce the notion we needed. But nobody loves it. That said, there are a lot of places (e.g., Drop, negative impls, perhaps |
This comment has been minimized.
This comment has been minimized.
|
I wanted to follow up on this discussion about "intuitionistic" vs "classical" logic. The fact that there exist types where neither To see what I mean, imagine that I have two impls like this: impl<T:!Clone> SomeTrait for T { .. }
impl<T:Clone> SomeTrait for T { .. }In principle, this seems equivalent to these two impls (which rely on specialization): impl<T> SomeTrait for T { .. }
impl<T:Clone> SomeTrait for T { .. }But in fact this is not the case. The reason is that, in the specialization case, we know that
then I can't say whether What this means is that if I wanted to use methods from fn foo<T>(t: &T) { MyTrait::some_method(t); }I would have to write one of the following: fn foo<T:MyTrait>(t: &T) { MyTrait::some_method(t); }
fn foo<T:Clone>(t: &T) { MyTrait::some_method(t); }
fn foo<T:!Clone>(t: &T) { MyTrait::some_method(t); }In any case, I know this RFC explicitly says that specialization is not an alternative, but the wording there didn't explicitly draw out this point about the excluded middle, and I thought it was non-obvious. Also, as an aside, I don't fully agree with this characterization:
In particular, while this is true of the existing specialization RFC, there exist specialization variants where the subset relationship is not known to hold (and I personally think those variants could be very important for us -- but that's a comment for another RFC). |
withoutboats
referenced this pull request
Jan 27, 2016
Open
Higher-ranked types in trait bounds #1481
This comment has been minimized.
This comment has been minimized.
|
So I wrote something: A modest proposal around negative reasoning and auto traits. I've been sitting on this write-up for a long time, hoping to edit it some more, but I've decided just to post it. As the title says, this is a "modest proposal" around negativity. I am not promoting this as an RFC yet -- for one thing, the material on auto traits is not quite right yet, though the differences are kind of in the weeds -- but it outlines my thinking about negativity quite well. Essentially, if we were to ever adopt a Frankly, I'm feeling pretty good about that proposal overall. But I know that others on @rust-lang/lang have some reservations. Let me try to summarize what I've heard:
|
This comment has been minimized.
This comment has been minimized.
It also lets you combine multiple blanket impls such as |
nikomatsakis
referenced this pull request
Mar 14, 2016
Open
Allow selecting multiple aggregate columns #3
aturon
referenced this pull request
Mar 23, 2016
Open
Tracking issue for specialization (RFC 1210) #31844
This comment has been minimized.
This comment has been minimized.
|
An example was posted in the subreddit today for which mutual exclusion would be well-suited, though it would also require another extension to the trait system surrounding trait Foo { }
impl<T> Foo for T where T: Copy { }
impl Foo for String { }The compiler disallows this (outside of the standard library) because it won't assume that no future version of The trait However there is a wrinkle in that
Both present a minor backward compatibility hazard in that removing all fields with a destructor is a breaking change. The first allows you to rely on that assumption in more cases. |
This comment has been minimized.
This comment has been minimized.
|
We discussed this RFC at the @rust-lang/lang meeting last week. We decided it makes sense to postpone this RFC for the time being, under issue #1053. Essentially, there isn't a lot of active conversation, and it makes sense to wait until the specialization implementation has progressed a bit farther before we make any decision here. As I wrote in my previous comment, though, I (personally) do think that there will be a role for negative reasoning, and that the broad outlines of this RFC are correct. |
nikomatsakis
referenced this pull request
Apr 8, 2016
Open
More flexible coherence rules that permit overlap #1053
nikomatsakis
closed this
Apr 8, 2016
kennytm
referenced this pull request
Jun 8, 2016
Closed
Automatically implement some traits for ! #1637
withoutboats
referenced this pull request
Jun 27, 2016
Closed
Revisiting specialization: Complementary traits #1658
withoutboats
referenced this pull request
Jul 12, 2016
Closed
Disjointness based on associated types. #1672
This comment has been minimized.
This comment has been minimized.
oberien
commented
Feb 11, 2017
|
Now that specialization is stable and released, what is the status on this RFC? |
This comment has been minimized.
This comment has been minimized.
|
Specialization is not stable. |
This comment has been minimized.
This comment has been minimized.
|
@oberien The ideas of this RFC are definitely something we're keeping in mind, but there are a lot of ways we might try to loosen the rules around coherence, so we're taking things a bit slowly. We're hoping to get specialization officially stabilized in the next couple of months; it's blocked on some pretty deep implementation work. After that, we can start looking more seriously at various extensions. |
This comment has been minimized.
This comment has been minimized.
|
@pitdicker just hit an issue needing negative type bounds or specialization (see here). |
This comment has been minimized.
This comment has been minimized.
|
@dhardy If a type cannot simultaneously implement trait BlockRng {
type Element;
type Results: AsRef<[Self::Element]> + Default;
}
impl<R> Rng for BlockRngWrapper<R>
where
R: BlockRng<Element=u32>,
R::Results: AsRef<[u32]> // <-- FIXME why is this line needed?
{ ... }
impl<R> Rng for BlockRngWrapper<R>
where
R: BlockRng<Element=u64>,
R::Results: AsRef<[u64]>
{ ... }Yes this still emits E0119 "conflicting implementations of trait", but at least we can guarantee that these two impls will never overlap using the more conservative #1672 instead of this RFC. (I guess all of these needs to wait for the |
This comment has been minimized.
This comment has been minimized.
newpavlov
commented
Jan 16, 2018
•
|
Another example of the need for mutually exclusive trait bound, or negative trait bound, or specialization with lattice rules, or something else in the line. In it I try to write support code which will allow us to use let result = for value in values {
// body
};To: let generator = IntoGenerator::into_generator(values);
let result = loop {
match generator.resume() {
GeneratorState::Yielded(value) => {
// body
},
GeneratorState::Complete(result) => break result,
}
};Quite unfortunately it seems this approach is currently impossible... |
withoutboats commentedJun 3, 2015
Mutually exclusive traits. Discussed previously on internals.rust-lang.org, but the RFC has been updated in material ways.
Rendered