Open nikomatsakis opened 8 years ago
Some additional open questions:
I am not sure that specialization changes the orphan rules:
Worse than that, the "future compatibility" orphan rules keep cross-crate specialization under pretty heavy control. Without them, default-impls leaving their methods open becomes much worse.
I never liked explicit negative reasoning. I think the total negative reasoning specialization provides is a nice compromise.
Should this impl be allowed with specialization as implemented? Or am I missing something? http://is.gd/3Ul0pe
Same with this one, would have expected it to compile: http://is.gd/RyFIEl
Looks like there's some quirks in determining overlap when associated types are involved. This compiles: http://is.gd/JBPzIX, while this effectively identical code doesn't: http://is.gd/0ksLPX
Here's a piece of code I expected to compile with specialization:
#![feature(specialization)]
use std::str::FromStr;
struct Error;
trait Simple<'a> {
fn do_something(s: &'a str) -> Result<Self, Error>;
}
impl<'a> Simple<'a> for &'a str {
fn do_something(s: &'a str) -> Result<Self, Error> {
Ok(s)
}
}
impl<'a, T: FromStr> Simple<'a> for T {
fn do_something(s: &'a str) -> Result<Self, Error> {
T::from_str(s).map_err(|_| Error)
}
}
fn main() {
// Do nothing. Just type check.
}
Compilation fails with the compiler citing implementation conflicts. Note that &str
doesn't implement FromStr
, so there shouldn't be a conflict.
@sgrif
I had time to look at the first two examples. Here are my notes.
First case, you have:
FromSqlRow<ST, DB> for T where T: FromSql<ST, DB>
FromSqlRow<(ST, SU), DB> for (T, U) where T: FromSqlRow<ST, DB>, U: FromSqlRow<SU, DB>,
The problem is that these impls overlap but neither is more specific than the other:
T: FromSql<ST, DB>
where T
is not a pair (so it matches the first impl but not the second).(T, U)
where:
T: FromSqlRow<ST, DB>
, U: FromSqlRow<SU, DB>
, but not(T, U): FromSql<(ST, SU), DB>
(T, U)
such that:
T: FromSqlRow<ST, DB>
U: FromSqlRow<SU, DB>
(T, U): FromSql<(ST, SU), DB>
This is the kind of situation that lattice impls would allow -- you'd have to write a third impl for the overlapping case, and say what it should do. Alternatively, negative trait impls might give you a way to rule out overlap or otherwise tweak which matches are possible.
You have:
Queryable<ST, DB> for T where T: FromSqlRow<ST, DB>
Queryable<Nullable<ST>, DB> for Option<T> where T: Queryable<ST, DB>
These overlap because you can have Option<T>
where:
T: Queryable<ST, DB>
Option<T>: FromSqlRow<Nullable<ST>, DB>
But neither impl is more specific:
T
such that T: FromSqlRow<ST, DB>
but T
is not an Option<U>
(matches first impl but not second)Option<T>
such that T: Queryable<ST, DB>
but not Option<T>: FromSqlRow<Nullable<ST>, DB>
@SergioBenitez
Compilation fails with the compiler citing implementation conflicts. Note that
&str
doesn't implementFromStr
, so there shouldn't be a conflict.
The problem is that the compiler is conservatively assuming that &str
might come to implement FromStr
in the future. That may seem silly for this example, but in general, we add new impls all the time, and we want to protect downstream code from breaking when we add those impls.
This is a conservative choice, and is something we might want to relax over time. You can get the background here:
Thank you for clarifying those two cases. It makes complete sense now
On Tue, Mar 22, 2016, 6:34 PM Aaron Turon notifications@github.com wrote:
@SergioBenitez https://github.com/SergioBenitez
Compilation fails with the compiler citing implementation conflicts. Note that &str doesn't implement FromStr, so there shouldn't be a conflict.
The problem is that the compiler is conservatively assuming that &str might come to implement FromStr in the future. That may seem silly for this example, but in general, we add new impls all the time, and we want to protect downstream code from breaking when we add those impls.
This is a conservative choice, and is something we might want to relax over time. You can get the background here:
- http://smallcultfollowing.com/babysteps/blog/2015/01/14/little-orphan-impls/
- rust-lang/rfcs#1023 https://github.com/rust-lang/rfcs/pull/1023
- rust-lang/rfcs#1053 https://github.com/rust-lang/rfcs/issues/1053
- rust-lang/rfcs#1148 https://github.com/rust-lang/rfcs/pull/1148
— You are receiving this because you were mentioned. Reply to this email directly or view it on GitHub https://github.com/rust-lang/rust/issues/31844#issuecomment-200093757
@aturon
The problem is that the compiler is conservatively assuming that &str might come to implement FromStr in the future. That may seem silly for this example, but in general, we add new impls all the time, and we want to protect downstream code from breaking when we add those impls.
Isn't this exactly what specialization is trying to address? With specialization, I would expect that even if an implementation of FromStr
for &str
were added in the future, the direct implementation of the Simple
trait for &str
would take precedence.
@SergioBenitez you need to put default fn
in the more general impl. Your
example isn't specializable.
On Tue, Mar 22, 2016, 6:54 PM Sergio Benitez notifications@github.com wrote:
@aturon https://github.com/aturon
The problem is that the compiler is conservatively assuming that &str might come to implement FromStr in the future. That may seem silly for this example, but in general, we add new impls all the time, and we want to protect downstream code from breaking when we add those impls.
Isn't this exactly what specialization is trying to address? With specialization, I would expect that even if an implementation of FromStr for &str were added in the future, the direct implementation for the trait for &str would take precedence.
— You are receiving this because you were mentioned. Reply to this email directly or view it on GitHub https://github.com/rust-lang/rust/issues/31844#issuecomment-200097995
I think "default" trait items being automatically considered default
sounds confusing. You might want both parametricity for a trait like in Haskell, etc. along side with easing the impl
s. Also you cannot easily grep
for them like you can for default
. It's not hard to both type the default
keyword and give a default implementation, but they cannot be separated as is. Also, if one wants to clarify the language, then these "default" trait items could be renamed to "trait proposed" items in documentation.
Note from #32999 (comment): if we do go with the lattice rule (or allow negative constraints), the "use an intermediate trait" trick to prevent further specialization of something will no longer work.
@Stebalien
Why won't it work? The trick limits the specialization to a private trait. You can't specialize the private trait if you can't access it.
@arielb1 Ah. Good point. In my case, the trait isn't private.
I don't think the "externals can't specialize because orphan forward-compatibility + coherence rulea" reasoning is particularly interesting or useful. Especially when we don't commit to our specific coherence rules.
Is there a way to access an overridden default impl
? If so, this could aid in constructing tests. See Design By Contract and libhoare.
Allowing projection of default associated types during type-checking will allow enforcing type inequality at compile-time: https://gist.github.com/7c081574958d22f89d434a97b626b1e4
#![feature(specialization)]
pub trait NotSame {}
pub struct True;
pub struct False;
pub trait Sameness {
type Same;
}
mod internal {
pub trait PrivSameness {
type Same;
}
}
use internal::PrivSameness;
impl<A, B> Sameness for (A, B) {
type Same = <Self as PrivSameness>::Same;
}
impl<A, B> PrivSameness for (A, B) {
default type Same = False;
}
impl<A> PrivSameness for (A, A) {
type Same = True;
}
impl<A, B> NotSame for (A, B) where (A, B): Sameness<Same=False> {}
fn not_same<A, B>() where (A, B): NotSame {}
fn main() {
// would compile
not_same::<i32, f32>();
// would not compile
// not_same::<i32, i32>();
}
edited per @burdges' comment
Just fyi @rphmeier one should probably avoid is.gd because it does not resolve for Tor users due to using CloudFlare. GitHub works fine with full URLs. And play.rust-lang.org works fine over Tor.
@burdges FWIW play.rust-lang.org itself uses is.gd for its "Shorten" button.
It can probably be changed, though: https://github.com/rust-lang/rust-playpen/blob/9777ef59b/static/web.js#L333
use like this(https://is.gd/Ux6FNs):
#![feature(specialization)]
pub trait Foo {}
pub trait Bar: Foo {}
pub trait Baz: Foo {}
pub trait Trait {
type Item;
}
struct Staff<T> { }
impl<T: Foo> Trait for Staff<T> {
default type Item = i32;
}
impl<T: Foo + Bar> Trait for Staff<T> {
type Item = i64;
}
impl<T: Foo + Baz> Trait for Staff<T> {
type Item = f64;
}
fn main() {
let _ = Staff { };
}
Error :
error: conflicting implementations of trait `Trait` for type `Staff<_>`: [--explain E0119]
--> <anon>:20:1
20 |> impl<T: Foo + Baz> Trait for Staff<T> {
|> ^
note: conflicting implementation is here:
--> <anon>:16:1
16 |> impl<T: Foo + Bar> Trait for Staff<T> {
|> ^
error: aborting due to previous error
Does feture specialization
support this, and is there any other kind of implementations currently?
@zitsen
These impls are not allowed by the current specialization design, because neither T: Foo + Bar
nor T: Foo + Baz
is more specialized than the other. That is, if you have some T: Foo + Bar + Baz
, it's not clear which impl should "win".
We have some thoughts on a more expressive system that would allow you to also give an impl for T: Foo + Bar + Baz
and thus disambiguate, but that hasn't been fully proposed yet.
If negative trait bounds trait Baz: !Bar
ever land, that could also be used with specialization to prove that the sets of types that implement Bar and those that implement Baz are distinct and individually specializable.
Seems @rphmeier 's reply is what I exactly want, impls for T: Foo + Bar + Baz
would also help.
Just ignore this, I still have something to do with my case, and always exciting for the specialization
and other features landing.
Thanks @aturon @rphmeier .
I've been playing around with specialization lately, and I came across this weird case:
#![feature(specialization)]
trait Marker {
type Mark;
}
trait Foo { fn foo(&self); }
struct Fizz;
impl Marker for Fizz {
type Mark = ();
}
impl Foo for Fizz {
fn foo(&self) { println!("Fizz!"); }
}
impl<T> Foo for T
where T: Marker, T::Mark: Foo
{
default fn foo(&self) { println!("Has Foo marker!"); }
}
struct Buzz;
impl Marker for Buzz {
type Mark = Fizz;
}
fn main() {
Fizz.foo();
Buzz.foo();
}
Compiler output:
error: conflicting implementations of trait `Foo` for type `Fizz`: [--explain E0119]
--> <anon>:19:1
19 |> impl<T> Foo for T
|> ^
note: conflicting implementation is here:
--> <anon>:15:1
15 |> impl Foo for Fizz {
|> ^
I believe that the above should compile, and there's two interesting variations that actually do work-as-intended:
1) Removing the where T::Mark: Fizz
bound:
impl<T> Foo for T
where T: Marker //, T::Mark: Fizz
{
// ...
}
2) Adding a "trait bound alias":
trait FooMarker { }
impl<T> FooMarker for T where T: Marker, T::Mark: Foo { }
impl<T> Foo for T where T: FooMarker {
// ...
}
(Which doesn't work if Marker
is defined in a separate crate (!), see this example repo)
I also believe that this issue might be related to #20400 somehow
EDIT: I've opened an issue about this: #36587
I'm encountering an issue with specialization. Not sure if it's an implementation problem or a problem in the way specialization is specified.
use std::vec::IntoIter as VecIntoIter;
pub trait ClonableIterator: Iterator {
type ClonableIter;
fn clonable(self) -> Self::ClonableIter;
}
impl<T> ClonableIterator for T where T: Iterator {
default type ClonableIter = VecIntoIter<T::Item>;
default fn clonable(self) -> VecIntoIter<T::Item> {
self.collect::<Vec<_>>().into_iter()
}
}
impl<T> ClonableIterator for T where T: Iterator + Clone {
type ClonableIter = T;
#[inline]
fn clonable(self) -> T {
self
}
}
(playpen) (by the way, it would be nice if this code eventually landed in the stdlib one day)
This code fails with:
error: method `clonable` has an incompatible type for trait:
expected associated type,
found struct `std::vec::IntoIter` [--explain E0053]
--> <anon>:14:5
|>
14 |> default fn clonable(self) -> VecIntoIter<T::Item> {
|> ^
Changing the return value to Self::ClonableIter
gives the following error:
error: mismatched types [--explain E0308]
--> <anon>:15:9
|>
15 |> self.collect::<Vec<_>>().into_iter()
|> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected associated type, found struct `std::vec::IntoIter`
note: expected type `<T as ClonableIterator>::ClonableIter`
note: found type `std::vec::IntoIter<<T as std::iter::Iterator>::Item>`
Apparently you can't refer to the concrete type of a defaulted associated type, which I find quite limiting.
@tomaka it should work, the RFC text has this:
impl<T> Example for T {
default type Output = Box<T>;
default fn generate(self) -> Box<T> { Box::new(self) }
}
impl Example for bool {
type Output = bool;
fn generate(self) -> bool { self }
}
(https://github.com/rust-lang/rfcs/blob/master/text/1210-impl-specialization.md#the-default-keyword)
Which seems similar enough to your case to be relevant.
@aatch that example doesn't seem to compile with the intuitive definition for the example trait: https://play.rust-lang.org/?gist=97ff3c2f7f3e50bd3aef000dbfa2ca4e&version=nightly&backtrace=0
the specialization code explicitly disallows this -- see #33481, which I initially thought was an error but turned out to be a diagnostics issue. My PRs to improve the diagnostics here went unnoticed, and I haven't maintained them to the latest master for quite some time.
@rphmeier the RFC text suggests that it should be allowed though, that example is copied from it.
I had a play with some code that could benefit from specialization. I strongly think we should go for the lattice rule rather chaining - it feels natural and was the only way to get the flexibility I needed (afaict).
If we went for default
on the impl
as well as individual items, could we enforce that if any item is overridden then they all must be? That would allow us to reason based on the precise type of a default assoc type (for example) in the other items, which seems like a useful boost in expressivity.
Should the following be allowed? I want to specialize a type so that ArrayVec is Copy
when its element type is Copy, and that it otherwise has a destructor. I'm trying to accomplish it by using an internal field that is replaced by specialization.
I hoped this would compile, i.e that it deduces the copyability of ArrayVec<A>
's fields from the field types that are selected by the A: Copy + Array
bound (compilable snippet on playground).
impl<A: Copy + Array> Copy for ArrayVec<A>
//where <A as Repr>::Data: Copy
{ }
The commented-out where clause is not wanted because it exposes a private type Repr
in the public interface. (It also ICEs anyway).
Edit: I had forgotten I reported issue #33162 about this already, I'm sorry.
Follow up on my comment, my actual use case:
// Ideal version
trait Scannable {}
impl<T: FromStr> Scannable for T {}
impl<T: FromStr> Scannable for Result<T, ()> {}
// But this doesn't follow from the specialisation rules because Result: !FromStr
// Lattice rule would allow filling in that gap or negative reasoning would allow specifying it.
// Second attempt
trait FromResult {
type Ok;
fn from(r: Result<Self::Ok, ()>) -> Self;
}
impl<T> Scannable for T {
default type Ok = T;
default fn from(r: Result<T, ()>) -> Self {...} // error can't assume Ok == T, could do this if we had `default impl`
}
impl<T> Scannable for Result<T, ()> {
type Ok = T;
default fn from(r: Result<T, ()>) -> Self { r }
}
fn scan_from_str<T: FromResult>(x: &str) -> T
where <T as FromResult>::Ok: FromStr // Doesn't hold for T: FromStr because of the default on T::Ok
{ ... }
// Can also add the FromStr bound to FromResult::Ok, but doesn't help
// Third attempt
trait FromResult<Ok> {
fn from(r: Result<Ok, ()>) -> Self;
}
impl<T> FromResult<T> for T {
default fn from(r: Result<Self, ()>) -> Self { ... }
}
impl<T> FromResult<T> for Result<T, ()> {
fn from(r: Result<T, ())>) -> Self { r }
}
fn scan_from_str<U: FromStr, T: FromResult<U>>(x: &str) -> T { ... }
// Error because we can't infer that U == String
let mut x: Result<String, ()> = scan_from_str("dsfsf");
@tomaka @Aatch
The problem there is that you are not allowed to rely on the value of other default items. So when you have this impl:
impl<T> ClonableIterator for T where T: Iterator {
default type ClonableIter = VecIntoIter<T::Item>;
default fn clonable(self) -> VecIntoIter<T::Item> {
// ^^^^^^^^^^^^^^^^^^^^
self.collect::<Vec<_>>().into_iter()
}
}
At the spot where I highlighted, clonable
is relying on Self::ClonableIter
, but because CloneableIter
is declared as default, you can't do that. The concern is that someone might specialize and override CloneableIter
but not clonable
.
We had talked about some possible answers here. One of them was to let you use default
to group together items where, if you override one, you must override all:
impl<T> ClonableIterator for T where T: Iterator {
default {
type ClonableIter = VecIntoIter<T::Item>;
fn clonable(self) -> VecIntoIter<T::Item> { ... }
}
}
This is ok, but a bit "rightward-drift inducing". The default
also looks like a naming scope, which it is not. There might be some simpler variant that just lets you toggle between "override-any" (as today) vs "override-all" (what you need).
We had also hoped we could get by by leveraging impl Trait
. The idea would be that this most often comes up, as is the case here, when you want to customize the return type of methods. So perhaps if you could rewrite the trait to use impl Trait
:
pub trait ClonableIterator: Iterator {
fn clonable(self) -> impl Iterator;
}
This would effectively be a kind of shorthand when implemented for a default group containing the type and the fn. (I'm not sure if there'd be a way to do that purely in the impl though.)
PS, sorry for the long delay in answering your messages, which I see date from July.
While impl Trait does help, there is no RFC that has been accepted or implemented which allows it to be used with trait bodies in any form, so looking to it for this RFC feels a bit odd.
I'm interested in implementing the default impl
feature (where all items are default
).
Would you accept a contribution on that?
@giannicic Definitely! I'd be happy to help mentor the work as well.
Is there currently a conclusion on whether associated types should be specializable?
The following is a simplification of my use-case, demonstrating a need for specializable associated types.
I have a generic data structure, say Foo
, which coordinates a collection of container trait objects (&trait::Property
). The trait trait::Property
is implemented by both Property<T>
(backed by Vec<T>
) and PropertyBits
(backed by BitVec
, a bit vector).
In generic methods on Foo
, I would like to be able to determine the right underlying data structure for T
via associated types, but this requires specialization to have a blanket impl for non-special cases as follows.
trait ContainerFor {
type P: trait::Property;
}
impl<T> ContainerFor for T {
default type P = Property<T>; // default to the `Vec`-based version
}
impl ContainerFor for bool {
type P = PropertyBits; // specialize to optimize for space
}
impl Foo {
fn add<T>(&mut self, name: &str) {
self.add_trait_obj(name, Box::new(<T as ContainerFor>::P::new())));
}
fn get<T>(&mut self, name: &str) -> Option<&<T as ContainerFor>::P> {
self.get_trait_obj(name).and_then(|prop| prop.downcast::<_>());
}
}
Thanks @aturon !
Basically I'm doing the work by adding a new "defaultness" attribute to the ast::ItemKind::Impl
struct (and then use the new attribute together with the impl item "defaultness" attribute) but there is also a quick and easy
possibility consisting on setting default to all the impl items of the default impl
during parsing.
To me this isn't a "complete" solution since we lost the information that the "defaultness" is related to the impl and not to each item of the impl,
additionally if there is a plan to introduce a partial impl
the first solution would already provide an attribute that can be used to store default
as well as partial
. But just to be sure and
not wasting time, what do you think about?
@giannicic @aturon may I propose we create a specific issue to discuss default impl
?
Never mind, I created one: https://github.com/rust-lang/rust/issues/37653
Would the lattice rule allow me to, given:
trait Foo {}
trait A {}
trait B {}
trait C {}
// ...
add implementations of Foo
for subset of types that implement some combination of A
, B
, C
, ...:
impl Foo for T where T: A { ... }
impl Foo for T where T: B { ... }
impl Foo for T where T: A + B { ... }
impl Foo for T where T: B + C { ... }
// ...
and allow me to "forbid" some combinations, e.g., that A + C
should never happen:
impl Foo for T where T: A + C = delete;
?
Context: I landed into wanting this when implementing an ApproxEqual(Shape, Shape)
trait for different kinds of shapes (points, cubes, polygons, ...) where these are all traits. I had to work around this by refactoring this into different traits, e.g., ApproxEqualPoint(Point, Point)
, to avoid conflicting implementations.
@gnzlbg
and allow me to "forbid" some combinations, e.g., that A + C should never happen:
No, this is not something that the lattice rule would permit. That would be more the domain of "negative reasoning" in some shape or kind.
Context: I landed into wanting this when implementing an ApproxEqual(Shape, Shape) trait for different kinds of shapes (points, cubes, polygons, ...) where these are all traits. I had to work around this by refactoring this into different traits, e.g., ApproxEqualPoint(Point, Point), to avoid conflicting implementations.
So @withoutboats has been promoting the idea of "exclusion groups", where you can declare that a certain set of traits are mutually exclusive (i.e., you can implement at most one of them). I envision this as kind of being like an enum (i.e., the traits are all declared together). I like the idea of this, particularly as (I think!) it helps to avoid some of the more pernicious aspects of negative reasoning. But I feel like more thought is needed on this front -- and also a good writeup that tries to summarize all the "data" floating around about how to think about negative reasoning. Perhaps now that I've (mostly) wrapped up my HKT and specialization series I can think about that...
@nikomatsakis :
So @withoutboats has been promoting the idea of "exclusion groups", where you can declare that a certain set of traits are mutually exclusive (i.e., you can implement at most one of them). I envision this as kind of being like an enum (i.e., the traits are all declared together). I like the idea of this, particularly as (I think!) it helps to avoid some of the more pernicious aspects of negative reasoning. But I feel like more thought is needed on this front -- and also a good writeup that tries to summarize all the "data" floating around about how to think about negative reasoning. Perhaps now that I've (mostly) wrapped up my HKT and specialization series I can think about that...
I thought about exclusions groups while writing this (you mentioned it in the forums the other day), but I don't think they can work since in this particular example not all traits implementations are exclusive. The most trivial example is the Point
and Float
traits: a Float
can be a 1D point, so ApproxEqualPoint(Point, Point)
and ApproxEqualFloat(Float, Float)
cannot be exclusive. There are other examples like Square
and Polygon
, or Box
| Cube
and AABB
(axis-aligned bounding box) where the "trait hierarchy" actually needs more complex constraints.
No, this is not something that the lattice rule would permit. That would be more the domain of "negative reasoning" in some shape or kind.
I would at least be able to implement the particular case and put an unimplemented!()
in it. That would be enough, but obviously I would like it more if the compiler would statically catch those cases in which I call a function with an unimplemented!()
in it (and at this point, we are again in negative reasoning land).
@gnzlbg lattice specialization would allow you to make that impl panic, but the idea of doing that makes me :cry:.
The idea of "exclusion groups" is really just negative supertrait bounds. One thing we haven't explored too thoroughly is the notion of reverse polarity specialization - allowing you to write a specialized impl that is of reversed polarity to its less specialized impl. For example, in this case you would just write:
impl<T> !Foo for T where T: A + C { }
I'm not fully sure what the implications of allowing that are. I think it connects to the issues Niko's already highlighted about how specialization is sort of conflating code reuse with polymorphism right now.
With all this discussion of negative reasoning and negative impls, I feel compelled to bring up the old Haskell idea of "instance chains" again (paper, paper, GHC issue tracker, Rust pre-RFC), as a potential source of inspiration if nothing else.
Essentially the idea is that anywhere you can write a trait
impl, you can also write any number of "else if clauses" specifying a different impl
that should apply in case the previous one(s) did not, with an optional final "else clause" specifying a negative impl (that is, if none of the clauses for Trait
apply, then !Trait
applies).
@withoutboats
The idea of "exclusion groups" is really just negative supertrait bounds.
I think that would be enough for my use cases.
I think it connects to the issues Niko's already highlighted about how specialization is sort of conflating code reuse with polymorphism right now.
I don't know if these can be untangled. I want to have:
impl
s for it to compile. Covering all cases is hard, but if the compiler forces me to cover all cases:
trait Foo {}
trait A {}
trait B {}
impl<T> Foo for T where T: A { ... }
impl<T> Foo for T where T: B { ... }
// impl<T> Foo for T where T: A + B { ... } //< compiler: need to add this impl!
and also gives me negative impls:
impl<T> !Foo for T where T: A + B { }
impl<T> !Foo for T where T: _ { } // _ => all cases not explicitly covered yet
I would be able to incrementally add impls as I need them and also get nice compiler errors when I try to use a trait with a type for which there is no impl.
I'm not fully sure what the implications of allowing that are.
Niko mentioned that there are problems with negative reasoning. FWIW the only thing negative reasoning is used for in the example above is to state that the user knows that an impl for a particular case is required, but has explicitly decided not to provide an implementation for it.
I just hit #33017 and don't see it linked here yet. It is marked as a soundness hole so it would be good to track here.
For https://github.com/dtolnay/quote/issues/7 I need something similar to this example from the RFC which doesn't work yet. cc @tomaka @Aatch @rphmeier who commented about this earlier.
trait Example {
type Output;
fn generate(self) -> Self::Output;
}
impl<T> Example for T {
default type Output = Box<T>;
default fn generate(self) -> Box<T> { Box::new(self) }
}
impl Example for bool {
type Output = bool;
fn generate(self) -> bool { self }
}
I stumbled upon the following workaround which gives a way to express the same thing.
#![feature(specialization)]
use std::fmt::{self, Debug};
///////////////////////////////////////////////////////////////////////////////
trait Example: Output {
fn generate(self) -> Self::Output;
}
/// In its own trait for reasons, presumably.
trait Output {
type Output: Debug + Valid<Self>;
}
fn main() {
// true
println!("{:?}", Example::generate(true));
// box("s")
println!("{:?}", Example::generate("s"));
}
///////////////////////////////////////////////////////////////////////////////
/// Instead of `Box<T>` just so the "{:?}" in main() clearly shows the type.
struct MyBox<T: ?Sized>(Box<T>);
impl<T: ?Sized> Debug for MyBox<T>
where T: Debug
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "box({:?})", self.0)
}
}
///////////////////////////////////////////////////////////////////////////////
/// Return type of the impl containing `default fn`.
type DefaultOutput<T> = MyBox<T>;
impl Output for bool {
type Output = bool;
}
impl<T> Example for T where T: Pass {
default fn generate(self) -> Self::Output {
T::pass({
// This is the impl you wish you could write
MyBox(Box::new(self))
})
}
}
impl Example for bool {
fn generate(self) -> Self::Output {
self
}
}
///////////////////////////////////////////////////////////////////////////////
// Magic? Soundness exploit? Who knows?
impl<T: ?Sized> Output for T where T: Debug {
default type Output = DefaultOutput<T>;
}
trait Valid<T: ?Sized> {
fn valid(DefaultOutput<T>) -> Self;
}
impl<T: ?Sized> Valid<T> for DefaultOutput<T> {
fn valid(ret: DefaultOutput<T>) -> Self {
ret
}
}
impl<T> Valid<T> for T {
fn valid(_: DefaultOutput<T>) -> Self {
unreachable!()
}
}
trait Pass: Debug {
fn pass(DefaultOutput<Self>) -> <Self as Output>::Output;
}
impl<T: ?Sized> Pass for T where T: Debug, <T as Output>::Output: Valid<T> {
fn pass(ret: DefaultOutput<T>) -> <T as Output>::Output {
<T as Output>::Output::valid(ret)
}
}
I am still working on https://github.com/dtolnay/quote/issues/7 and needed a diamond pattern. Here is my solution. cc @zitsen who asked about this earlier and @aturon and @rphmeier who responded.
#![feature(specialization)]
/// Can't have these impls directly:
///
/// - impl<T> Trait for T
/// - impl<T> Trait for T where T: Clone
/// - impl<T> Trait for T where T: Default
/// - impl<T> Trait for T where T: Clone + Default
trait Trait {
fn print(&self);
}
fn main() {
struct A;
A.print(); // "neither"
#[derive(Clone)]
struct B;
B.print(); // "clone"
#[derive(Default)]
struct C;
C.print(); // "default"
#[derive(Clone, Default)]
struct D;
D.print(); // "clone + default"
}
trait IfClone: Clone { fn if_clone(&self); }
trait IfNotClone { fn if_not_clone(&self); }
impl<T> Trait for T {
default fn print(&self) {
self.if_not_clone();
}
}
impl<T> Trait for T where T: Clone {
fn print(&self) {
self.if_clone();
}
}
impl<T> IfClone for T where T: Clone {
default fn if_clone(&self) {
self.clone();
println!("clone");
}
}
impl<T> IfClone for T where T: Clone + Default {
fn if_clone(&self) {
self.clone();
Self::default();
println!("clone + default");
}
}
impl<T> IfNotClone for T {
default fn if_not_clone(&self) {
println!("neither");
}
}
impl<T> IfNotClone for T where T: Default {
fn if_not_clone(&self) {
Self::default();
println!("default");
}
}
Hit a bug (or at least unexpected behavior from my perspective) with specialization and type inference: #38167
This is a tracking issue for specialization (rust-lang/rfcs#1210).
Major implementation steps:
default impl
(https://github.com/rust-lang/rust/issues/37653)default
members? https://github.com/rust-lang/rust/issues/48444Unresolved questions from the RFC:
default type
? Never during typeck? Or when monomorphic?default
(i.e. specializable)?default impl
(where all items aredefault
) orpartial impl
(wheredefault
is opt-in); see https://github.com/rust-lang/rust/issues/37653#issuecomment-616116577 for some relevant examples of wheredefault impl
is limiting.Note that the
specialization
feature as implemented currently is unsound, which means that it can cause Undefined Behavior withoutunsafe
code.min_specialization
avoids most of the pitfalls.