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 upRFC: impl specialization #1210
Conversation
aturon
changed the title
RFC: Impl specialization
RFC: impl specialization
Jul 13, 2015
aturon
added
the
T-lang
label
Jul 13, 2015
aturon
self-assigned this
Jul 13, 2015
sfackler
reviewed
Jul 13, 2015
| } | ||
| partial impl<T: Clone, Rhs> Add<Rhs> for T { | ||
| fn add_assign(&mut self, rhs: R) { |
This comment has been minimized.
This comment has been minimized.
alexcrichton
reviewed
Jul 13, 2015
| The solution proposed in this RFC is instead to treat specialization of items in | ||
| a trait as a per-item *opt in*, described in the next section. | ||
|
|
||
| ## The `default` keyword |
This comment has been minimized.
This comment has been minimized.
alexcrichton
Jul 13, 2015
Member
This mentions that default will be a keyword, but we currently have modules like std::default and methods like Default::default, so I think adding this as a keyword may be a breaking change (same with partial below). Could this RFC perhaps clarify that they'll be contextual keywords? I believe that should be backwards compatible, right?
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
bstrie
Aug 1, 2015
Contributor
Making them contextual keywords would be BC, but contextual keywords themselves are unprecedented in the language. Unfortunate, but probably inevitable.
This comment has been minimized.
This comment has been minimized.
|
How does the compiler handle code such as this: struct Foo<T>;
impl<T> Foo<T> {
default fn test(self) { ... }
}
fn bar(Foo<u32> p) {
p.test();
}It has no way of knowing whether a downstream crate will provide a more specialized implementation of edit: |
This comment has been minimized.
This comment has been minimized.
|
I'd imagine downstream crates can't specialize inherent methods of a type, just as they can't add inherent methods to a type today. |
This comment has been minimized.
This comment has been minimized.
|
I'm probably gonna come back to this a few times over the next couple days because I feel like there is a lot to chew on here, and even now reading it for the third time I have some things I want to think about more deeply. Overall I like this approach and think that the specified algorithm is a good point in the design space. As I mentioned on IRC I think we should also follow up with a proposal for a detailed implementation strategy, that we (the compilers team, core team, me, any other relevant parties) talk about for a period of time. From my perspective (and I hope others too) it is important that we evaluate our implementation strategy and ensure that it isn't going to cause problems down the road in terms of stability, ICEs, our future compiler design (incremental, parallel, etc). |
This comment has been minimized.
This comment has been minimized.
|
This isn't relevant to the current RFC but an alternative explicit ordering mechanism would be a match like syntax: impl<'a, T: ?Sized, U: ?Sized> AsRef<U> for {
&'a T where T: AsRef<U> => {
fn as_ref(&self) -> &T {
<T as AsRef<U>>::as_ref(*self)
}
},
T where T == U => {
fn as_ref(&self) -> &T {
self
}
},
} |
This comment has been minimized.
This comment has been minimized.
stevenblenkinsop
commented
Jul 14, 2015
|
Another alternative [edit] to explicit ordering [/edit] would be to break the overlap using negative bounds. Obviously this is contingent on negative bounds being accepted. This seems like a better approach, since it preserves the intuitive rule used in this proposal along with its various properties. Also, allowing edit: Clarified that I'm talking about an alternative to explicit ordering, not to specialization. |
mdinger
reviewed
Jul 14, 2015
| ``` | ||
|
|
||
| This partial impl does *not* mean that `Add` is implemented for all `Clone` | ||
| data, but jut that when you do impl `Add` and `Self: Clone`, you can leave off |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
I'm not very comfortable with the idea of introducing a mechanism which can create very tall inheritance trees; if the number of possible Would it be possible therefore to limit the number of I don't think negative bounds and specialization are alternatives to one another at all. This RFC doesn't mention PR #1148 which enables negative bounds without the backcompat hazard that troubled PR #586. I think that these would be complementary changes to the coherence rules which address one another's limitations. Not trying to trumpet my own RFC -- just pointing out that is a related proposal. Haskell has extensions which implement some form of specialization (e.g. OverlappingInstances). It would be a good idea probably to ask in the Haskell community about the pitfalls that implementations of type class specialization have run into. I think this is a situation in which Rust's more OO-influenced heritage makes specialization more useful for us than it was for Haskell though. We'll probably need a new name for Regardless of these, this is an awesome and impressively exhaustive RFC! |
llogiq
reviewed
Jul 14, 2015
|
|
||
| ```rust | ||
| impl<T> Debug for T where T: Display { | ||
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
stevenblenkinsop
Jul 14, 2015
The text hasn't motivated default at this point. The example is of what the proposal is trying to allow "at the simplest level"—i.e. before the complexity of needing a default keyword is added—not of what the proposed syntax will ultimately look like once this complexity is taken into account. I thought this was clear, but maybe it could be clarified.
It might be a good idea to limit the appearance of motivating examples which don't follow the proposed syntax. One way would just be to add text saying "ignore the default keyword for now, it'll be motivated later". This would be unfortunate though, since I liked the style of exposition used here, and adding these caveats would diminish it somewhat. Perhaps a better option is just to add a comment in the example itself saying:
// Note: This example will not work as written under this proposal.
This comment has been minimized.
This comment has been minimized.
pnkfelix
Jul 16, 2015
Member
At the very least, any example that does not actually follow the expected end-syntax could have an explicit annotation saying so, perhaps with a pointer to an end-appendix that shows each such example in the final expected form.
(Also, the "Motivation" section did at least say default in one example -- so there is at least precedent for using it here as well, if one does not want to go the route of adding an appendix with all of the examples according to their final formulation)
llogiq
reviewed
Jul 14, 2015
| - You have to lift out trait parameters to enable specialization, as in the | ||
| `Extend` example above. The RFC mentions a few ways of dealing with this | ||
| limitation -- either by employing inherent item specialization, or by | ||
| eventually generalizing HRTBs. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
Overall, I'm quite happy with the proposal; I'm a bit worried about corner cases (especially regarding dropck), but I think we should be able to sort them out. Of course, once this RFC is accepted, Rust will cease to even be a language of medium complexity. People will misuse this feature to create a maze of twisty little Therefore the only thing I don't like is the use of the default keyword (despite having it in Java interfaces). I want the keyword to be long, outlandish and hard to remember, so folks will have to think twice before writing it. Something like: |
This comment has been minimized.
This comment has been minimized.
bill-myers
commented
Jul 14, 2015
|
Requiring that apply(I) and apply(J) are either disjoint or one contained in the other seems excessively restrictive. A less restrictive rule could be this set of equivalent rules:
This allows sets of impls like this:
This would be allowed since the intersection of apply(B) and apply(C) is equal to apply(D), the only apply-set contained in both, and all other apply-set pairs are contained in each other. Not sure about the algorithmic complexity of checking for this though. It appears to be equivalent to SAT solving, but this also applies to checking the exhaustiveness of match patterns, so it's not necessarily an issue. A possible middle ground is to require that the intersection of apply(I) and apply(J) is equal to apply(K) for just one K, which should eliminate the SAT equivalency, and might still be expressive enough. |
This comment has been minimized.
This comment has been minimized.
|
Another motivating example is the |
This comment has been minimized.
This comment has been minimized.
|
I like it! My only worry is the that the language is getting more complex, but arguable thats not avoidable in this case. Am I right in thinking that this RFC would enable backwards-compatibly solving the partial impl<T> Clone for T where T: Copy {
default fn clone(&self) -> Self {
*self
}
} |
This comment has been minimized.
This comment has been minimized.
|
Wow, this basically destroys dropck - we may have to go back to a (maybe compiler-guaranteed) form of No. That requires "lattice impls", which is explicitly not part of this RFC. |
This comment has been minimized.
This comment has been minimized.
|
@arielb1 please elaborate how this 'destroys dropck'. |
This comment has been minimized.
This comment has been minimized.
|
It completely destroys parametricity, and therefore dropck. I think it may even be a better option to make specialization unsafe for that reason. To see how it destroys parametricity: Suppose you have a completely innocent blanket impl for a impl<'a, T> Clone for &'a T { fn clone(&self) -> Self { *self } }It can be trivially called by a destructor: struct Zook<T>(T);
fn innocent<T>(t: &T) { <&T as Clone>::clone(&t) /* completely innocent, isn't it? */; }
impl<T> Drop for Zook<T> { fn drop(&mut self) { innocent(&self.0); } }However, this can be abused: struct Evil<'a>(&'b OwnsResources /* anything that is invalid after being dropped */);
impl<'a,'b> Clone for &'a Evil<'b> {
fn clone(&self) -> Self {
println!("I can access {} even after it was freed! muhahahaha", self.0);
loop {}
}
}
fn main() {
let (zook, owns_resources);
owns_resources = OwnsResources::allocate();
zook = Zook(Evil(&owns_resources));
} |
This comment has been minimized.
This comment has been minimized.
Isn't this problem what the |
This comment has been minimized.
This comment has been minimized.
@nikomatsakis, @pnkfelix and I had discussed the dropck issue a while back (note the brief discussion in Unresolved Questions). There are a few avenues for preventing the interaction you're describing -- note, for example, that in the RFC proposal you need a
The RFC talks a bit about how we could handle this case -- in particular, the overlap/specialization requirements for |
This comment has been minimized.
This comment has been minimized.
|
Note that ruling out bad dropck interaction is nontrivial because of examples like the following: trait Marker {}
trait Bad {
fn foo(&self);
}
impl<T: Marker> Bad for T {
default fn foo(&self) {}
}
impl<'a, T> Marker for &'a T {}Here, given an arbitrary UPDATE: the main questions here are: can we convince ourselves that such a restriction retains the needed parametricity for dropck? And does such a restriction still support the main use cases for specialization? This is why I wanted to experiment a bit more before laying out a detailed proposal for the restriction. |
This comment has been minimized.
This comment has been minimized.
|
Wouldn't Also, "nontrivial bound": the absence of |
This comment has been minimized.
This comment has been minimized.
|
Even more fun from Unsound Labs: this useful-ish and rather innocent code: use std::fmt;
pub struct Zook<T>(T);
impl<T> Drop for Zook<T> { fn drop(&mut self) { log(&self.0); } }
fn log<T>(t: &T) {
let obj = Object(Box::new(t));
println!("dropped object is {:?}", obj);
}
struct Object<T>(Box<T>);
impl<T> fmt::Debug for Object<T> {
default fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "<instance at {:?}>", (&*self.0) as *const T)
}
}
impl<T: fmt::Debug> fmt::Debug for Object<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
self.0.fmt(f)
}
}can be exploited by this wolf in sheep's clothing (not even a single evil structure in sight!): fn main() {
let (zook, data);
data = vec![1,2,3];
zook = Zook(Object(Box::new(&data)));
}enjoy! |
This comment has been minimized.
This comment has been minimized.
bluss
commented
Jul 14, 2015
|
@arielb1 Do you have any idea for how we can provide simple specialization? An example is |
This comment has been minimized.
This comment has been minimized.
|
@bluss That sounds like one of the things the optimizer should be able to do reliably. |
This comment has been minimized.
This comment has been minimized.
bluss
commented
Jul 14, 2015
|
@tbu-: it does not (LLVM has no loop idiom recognize for memcmp in this case), and it's one of the example motivations for specialization. |
This comment has been minimized.
This comment has been minimized.
|
That should probably also be filed for LLVM, then. |
aturon commentedJul 13, 2015
•
edited by mbrubeck
This RFC proposes a design for specialization, which permits multiple
implblocks to apply to the same type/trait, so long as one of the blocks is clearly
"more specific" than the other. The more specific
implblock is used in a caseof overlap. The design proposed here also supports refining default trait
implementations based on specifics about the types involved.
Altogether, this relatively small extension to the trait system yields benefits
for performance and code reuse, and it lays the groundwork for an "efficient
inheritance" scheme that is largely based on the trait system (described in a
forthcoming companion RFC).
Rendered