Repository navigation
combined trait/impl syntax #3048
Description
Activity
It's still possible to have trait-less implementations that apply to non-class types; that hasn't changed. For an example of one currently in use in the compiler, search for
impl methods for @fn_ctxtin https://github.com/mozilla/rust/blob/incoming/src/rustc/middle/typeck/check.rs. The receiver type,fn_ctxt, is defined in that file, and isn't a class. There are several other examples in the compiler. Am I misunderstanding the issue?It is only possible for nominal types, strictly speaking, which basically means classes or enums.
@lkuper thanks for the pointer. I think something has changed, but slightly differently to how I suggested.
This is my current understanding:
Before, you could define an iface-less impl on any type and the definition could be in any scope. These impls behaved just like normal impls (that had ifaces).
Currently, you can define a trait-less impl on any nominal (or pointer-to nominal?) type so long as the definition has to be in the same scope as the type. Going by the error messages I have seen, i am guessing these impls have a different semantics to normal impls - they are somehow "inherent" methods that are more attached to the type than regular trait methods are.
My guess is that methods defined inside class bodies are going to be removed and replaced with trait-less impls defined in the same scope. This would allow defining "inherent" methods on enums, something that was not possible before.
I found the previous semantics of iface/trait-less impls really nice. It is no fun to have to define a trait and then directly below it duplicate all the method signatures in the single impl of that trait.
As a solution to this problem, could inherent methods be defined only within class bodies and enum bodies? Currently you cant defined methods inside enums, but why not? This would allow the trait-less impl syntax to have the semantics that iface-less impls had before.
You can already define an impl without a trait for enums/classes, but you must do so within the same module (or submodule). I don't see how allowing one to define methods inside an enum body would help the problem I thought you were facin. Actually defining methods within a class body is deprecated in any case.
Maybe I misunderstood what you wanted? I figured you wanted to define new trait-less methods from some other place besides the definition of the receiver type.
I figured you wanted to define new trait-less methods from some other place besides the definition of the receiver type.
That is correct. Sorry for not being clear.
I am just trying to suggest a way to allow trait-less impls to have the semantics that they did before - by introducing different ways to implement the semantics that they have now. But I have made some assumptions that probably are not correct, confusing matters.
Before, you could define an iface-less impl on any type and the definition could be in any scope. These impls behaved just like normal impls (that had ifaces).
Currently, you can define a trait-less impl on any nominal (or pointer-to nominal?) type so long as the definition has to be in the same scope as the type. Going by the error messages I have seen, i am guessing these impls have a different semantics to normal impls - they are somehow "inherent" methods that are more attached to the type than regular trait methods are.@Dretch Could it be that you're butting heads with the new instance coherence enforcement? (See discussion of instance coherence on the development roadmap, and also here.)
I found the previous semantics of iface/trait-less impls really nice. It is no fun to have to define a trait and then directly below it duplicate all the method signatures in the single impl of that trait.
I think you shouldn't have to do that once default methods are allowed in traits, which is a feature that's on the way.
@Dretch Could it be that you're butting heads with the new instance coherence enforcement? (See discussion of instance coherence on the development roadmap, and also here.)
Perhaps, but I am hopeful that the trait-less impls (where the trait to implement is kind-of anonymous and is created automatically by declaring the impl - that is how I understood the old iface-less impls) are compatible with the new coherence rules.
I think you shouldn't have to do that once default methods are allowed in traits, which is a feature that's on the way.
As I understand if the default/provided methods in a trait would only be allowed to call other (required/provided) methods in that trait, so they are not equivalent to trait-less impls, which can call any methods/functions defined for the type.
@Dretch Can you gist a concrete piece of code that used to work and doesn't now, so I can get a clear idea of the problem? Thanks.
@Dretch Can you gist a concrete piece of code that used to work and doesn't now, so I can get a clear idea of the problem? Thanks.
Before, I could write (this is in a util module in a project of mine):
impl option_extensions for option<~str> { /// if the specified option is some(msg), then fail with msg fn fail_if_some () { for self.each |msg| { fail msg } } }But now I have to write:
trait option_extensions { fn fail_if_some(); } impl option_extensions of option_extensions for option<~str> { /// if the specified option is some(msg), then fail with msg fn fail_if_some () { for self.each |msg| { fail msg } } }It is not that it doesn't work any more - it works fine - the problem (IMHO) is that it requires more duplication/boilerplate.
Would it be fine to have a syntax that declared a trait and an impl in one go?
Would it be fine to have a syntax that declared a trait and an impl in one go?
I think it would, yes please!
So, for the record: yes, the issue is caused by instance coherence enforcement, and no, adding default methods to traits won't solve it. But @pcwalton's suggestion, of a combined trait/impl syntax, would. Maybe it should just look like this (to go back to @Dretch's example):
trait option_extensions for option<~str> { /// if the specified option is some(msg), then fail with msg fn fail_if_some () { for self.each |msg| { fail msg } } }And this wouldn't be possible with merely traits + default methods, because parametricity requires that provided methods can only use other provided/required methods.
So, tentatively changing the title of this bug to "combined trait/impl syntax"...
+1 on this, I just hit it again. not a huge deal but it'll keep coming up. it's primarily how you extend a base trait with purely-derived methods.
I am going to put this on the 0.4 milestone because I think it's a fairly big papercut issue.
Perhaps we can use a syntax extension
nominating for backwards-compatible milestone
consensus of team is closing WONTFIX.
- added a commit that references this issue
on May 15, 2021 - added a commit that references this issue
on Sep 3, 2023 - added a commit that references this issue
on Jun 4, 2024
Rust used to support iface-less impls. These were good because they avoided duplicating method signatures.
Since the iface -> trait conversion these no longer seem to be supported. Instead, it seems, a trait-less impl can only apply to class types and is equivalent to declaring the methods inside the class body.
IMO the iface/trait-less impl was really useful. Having to declare the iface/trait even though there will only ever be one impl seems redundant.
Is there any chance this feature could return?