-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Automatically implement some traits for ! #1637
Conversation
What about writing
instead of the attribute? The double |
Why not the other way around? Opt-into implementing
Then there would be no surprise that a trait implements Finally, I don't like that my program's behavior is influenced by seemingly unrelated changes, such as adding a static method causing the |
Good point. That's a much better way of doing it.
Because it would be too intrusive having to appear on most traits for something that rarely gets used. In practice, I think most people will just leave it off and then their traits won't be implemented for
Remember that this is all dead code and that these impls don't do anything. I don't think the compiler needs to be at all picky if someone wants to use let x: u32 = match opt { Some(y) => y, None => panic!("ahh") as u32, }
They could still get this error message anyway if the trait isn't explicitly implemented for |
I'm moderately in favor of #1216 because if we have |
Why not? trait PreHasher {
type H: Hasher;
fn into_hasher(self) -> io::Result<Self::H>;
} But then I want to make a dummy implementor of this trait which always errors. struct ErroringPreHasher;
impl PreHasher for ErroringPreHasher {
type H = !;
fn into_hasher(self) -> io::Result<!> { ... }
} Why shouldn't this be allowed?
Well, changing the definition of a trait will already break all the users of that trait in most cases. And presumably the user will get an error message telling them why the impl couldn't be derived. |
occurs in dead code anyway. The author sees this RFC as a kind-of extension | ||
of this behaviour. | ||
* People who aren't aware of this feature might be surprised to learn that | ||
their trait implements `!`. In most cases this won't be a huge problem since |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: "... to learn that !
implements their trait"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Fixed.
Because special cases aren't necessarily special enough to break the rules. You don't need to convince me that this RFC is sound, I can see that. What I don't agree is that the use case for adding these special rules justifies the additional complexity of the language in having these special rules. |
I largely agree with @withoutboats. I also want to raise a specific area of nervousness: we've talked about possible extensions to the trait system like negative bounds or mutually-exclusive traits, and the latter in particular would need to be done carefully if we also had auto-implementation. I also feel uneasy about the fact that this would only apply to some/most traits -- it feels like nontrivial complexity for something that will probably be pretty obscure in practice. More broadly, though, I'm inclined to reach a decision on #1216, get some experience using it, and then determine from that experience how well-motivated this RFC is. I think it'd be premature to reach a decision on this RFC until then. (And on that note, think we should postpone this RFC for the time being.) |
I agree with @withoutboats that I don't yet see strong reasons to add such impls. It seems like something we could do over time (and on a piecemeal basis) if we chose, perhaps eventually adopting a more automatic proposal like this. I am overall nervous about this proposal in a number of ways. First, the interaction with other parts of the (already complex) trait system, as @aturon pointed out, but also the fact that we can't write these "automatic" impls for all traits, only a kind of random subset that doesn't align with other things in the language (it sort of aligns with object-safe, I suppose, since -- like objects -- it requires an instance of the type for the impl to make sense and be usable). It also may be a subset that continues to evolve: e.g., what about associated constants? Other sorts of associated items? |
@aturon's point about negative bounds seems particularly apt. Why should The right implementation of this proposal seems to me to be some sort of special rule in which |
@kennytm Yes, I do. Fixed, thanks! |
I whole-heartedly agree.
If they can't be inferred then the impl as a whole can't be inferred.
Maybe I don't understand what you're proposing here but this doesn't sound right at all. If a trait has static methods or associated types/constants then the compiler has no idea how the user might want to define them for /// Types that have a dual type
trait Dual {
type D: Dual
}
impl Dual for () {
type D = !;
}
impl Dual for ! {
type D = ();
}
/// Format the name of a type
impl DebugTypename {
fn fmt(f: &mut fmt::Formatter) -> fmt::Result;
}
impl DebugTypename for () {
fn fmt(f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "()")
}
}
impl DebugTypename for ! {
fn fmt(f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "!")
}
} The whole point of this RFC is that it only works in cases like |
But in the presence of negative impls, a user does have a choice - they could impl |
I ran into some concrete use cases for this recently. I wanted to call a noreturn function (declared with This seems entirely safe due to Rust's guarantee that code involving |
Regarding the issue about negative / positive impls, has anyone with sufficient knowledge in type theory (which I certainly don't have) considered the possibility that |
@joshtriplett Can't you cast the result to a concrete type that impl that trait? Something like fn main() {
let f: u32 = panic!(""); // <- u32: Clone
f.clone();
} |
@golddranks I am not so sure – in dependently typed languages the mere existence of a diverging value often allows one to obtain a proof of anything, which allows one to coerce incompatible types; at runtime, the diverging proof term has been erased, but the unsafe cast hasn't, resulting in undefined behavior. I don't think this is an issue for Rust or for any reasonable future extension – but it would take someone with experience in type theory to say for certain. |
@drbo (I don't know whether or how this relates to @golddranks's suggestion; just for the record.)
This is true even in non-dependently-typed languages such as Rust. If you obtain a "value" of What's different about dependently typed languages (although note that this is a human-created coincidence, not a fundamentally required property of them) is that most of them aim to be not just type-safe, but also logically consistent. What this means is that an expression of type |
Actually, the difference is that in dependently typed languages, the bottom
|
Okay, I think I understand what you're saying: unlike in Rust, which uses strict evaluation everywhere, the dependently typed language doesn't (always) evaluate the given term to normal form, because it assumes that, due to logical consistency, one has to exist anyways, and so computing it would be needless busywork? (I suppose "the proof term is erased" is another way of saying the same thing, I hadn't made the connection.) This sounds like basically the same issue that Richard Eisenberg describes for GHC's type families. (Again though, I think this is a property of the dependently typed languages that happen to exist and are popular, not something that necessarily follows from being dependently typed?) |
I second @golddranks's suggestion that pub trait Consumable {
fn consume(&self);
}
pub trait Edible: !Poisonous {
fn nourish(&self);
}
pub trait Poisonous: !Edible {
fn sicken(&self);
}
impl<T> Consumable for T where T: Edible {
fn consume(&self) { self.nourish() }
}
impl<T> Consumable for T where T: Poisonous {
fn consume(&self) { self.sicken() }
} there is no problem with pub trait MyTrait {
fn do_it();
}
pub trait Marker {}
impl<T> MyTrait for T where T: Marker {
fn do_it() { println!("Called on Marker"); }
}
impl<T> MyTrait for T where T: !Marker {
fn do_it() { println!("Called on !Marker"); }
} In this case I have a background in proof theory, and the "type impls trait" judgement is like a provability assertion, which composes nicely. In contrast, unprovability assertions do not, which reflects the fact that later trait impls can cause a
The mathematical equivalent here is that of an inconsistent system. It should not be a surprise that The linked RFC is also not clear on how far "reverse deduction" is permitted for trait bounds. If My view is that negative impls (at least with the given RFC) are type-theoretically unsound, and |
I don't know what you mean by "innate" but the problem with allowing types to implement both
To me this conclusion is non sequitur from the rest of your post, could you elaborate? I suggest we discuss this further on #1658, where it would be much more on topic than on this RFC's discussion thread. |
@withoutboats I'll just give a quick response, my post got away from me a bit there.
I was trying to reason out the logical consequence of a type for which
Roughly, the notion Anyway, I'll stop here and migrate to #1658 (which I should note differs from the proposal that I was referencing above). |
There's a compromise position that I think everyone could support: what if we allowed trait impl method definitions to be omitted when they have an argument of an uninhabited type? For example:
This would allow you to impl
Of course this still comes with the problem that people will need to explicitly say that they want the impl which is largely what this RFC was trying to avoid. |
@canndrew This method also requires explicit impls, which means that you can't "fix someone's oversight" if, say, libstd didn't decide to |
@digama0 I would still want the full form of this RFC (It think, and only if it's compatible with other future features like negative trait bounds). The good thing about the compromise idea though is that it's forwards-compatible with this RFC. |
@canndrew I still don't see the motivation. Why should we expect that implementing traits for |
It is true that as long as the trait impl is being written out, there isn't
since |
Agreed. I also agree that in the presence of negative impls this whole thing is questionable. But without negative impls I can't see why you'd want the compiler to not just assume the impls.
And then when people want to use the impls they won't be there. But yes, this is all very hypothetical and we need to implement |
The lang team has moved the @canndrew, you've previously agreed with this sentiment. Would you be willing to close out this RFC for now, to be potentially reconsidered later if the lack of trait impls turns out to be a significant problem in practice? |
@aturon what does lang team plan to do about EDIT: eh, wrong RFC, never mind me. |
@aturon Sure. |
I've opened an RFC for the more conservative "compromise position" here |
This RFC assumes the
!
-type RFC as a prerequisite.Rendered