-
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
Trait based inheritance #223
Conversation
Moreover, in comparison to other proposals for inheritance, the design should work | ||
well with existing Rust features and follow Rust's philosophies: | ||
|
||
* There should be no new ways to acheive the same behavior. For example, virtual calls |
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.
s/acheive/achieve/
👍 This is how Rust inheritance should work. This level of flexibility could even allow for bindings with other object models- various C++ layouts (potentially even with multiple inheritance), COM, etc. |
How will safe downcasting work with lifetime parameters? In the example, For downcasting to be safe it seems that all members of a class hierarchy need to have identical lifetime parameters, the copy of a parent class in a child class needs to be instantiated with the same parameters as the child, and the downcasting operation needs to respect these parameters. |
I believe the issue with these lifetimes that seem to pop out of thin air was fixed by #192. Basically, because I'll try to update the examples to make this more clear. |
+1 to this proposal. As pointed out in the proposal, the traits here can be the low level building blocks, providing maximum flexibility, while commonly used inheritance patterns can be codified in macros. (Sort of like Ecmascript 6's It will make people think twice before using inheritance, simply because the relative verbosity and the "ugliness" of macro invocations compared to dedicated syntax. I consider this a plus and don't think we need to artifically limit inheritance in any other way. (IMHO, #142 needs limitations partly because the syntax there is too lightweight and pretty. That can be both an advantage and a disadvantage.) |
that something of type `A` can be converted to something of type `B` safely with a | ||
simple `transmute`. To actually use this ability, a function is added to the standard | ||
library as follows: | ||
|
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.
Cast is almost the wrong name for the trait, because casting in both Rust and C is a conversion preserving the value, not something like a blind transmute.
@gereeter That approach doesn't work with multiple lifetime parameters, does it? Also, what about non-lifetime type parameters? |
@zwarich The same approach should work fine for multiple lifetime parameters: in the trait object case, the object would have to have all the lifetime bounds, and in the reference case, any reference would have to outlive all lifetimes involved. For non-lifetime type parameters, the parameter would be part of the RTTI. |
} | ||
``` | ||
|
||
## Summary example |
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.
For easy comparison with the other proposals for handling DOM-data structures, could you show how https://gist.github.com/jdm/9900569 would look encoded with this proposal please?
@gereeter, could you add an example of how https://gist.github.com/jdm/9900569 might be encoded with this RFC? The example at the end looks similar, but not quite the same. |
6357402
to
e0acdf4
Compare
|
||
|
||
fn dump<'a>(node: NodeBox<'a>) { | ||
if let Ok(text_node): Option<&TextNodeBox<'a>> = downcast_copy(node) { |
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.
Is this cleverly crossborrowing node
into a shared reference? (Probably should be Some(...)
, not Ok(...)
)
Bikeshedding. Could the following syntax work? Here @Coerce should "magically" ensure that the layout is fixed.
Perhaps this could open the door for future self inspection syntax..
|
|
||
``` | ||
impl <U, T> Cast<Bundle<U>> for Bundle<U, T> {} | ||
impl <U: Cast<U2>, T: Extend<T2>> Cast<Bundle<U2, T2>> for Bundle<U, T> {} |
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.
Is this correct? It seems like it should either be T: Cast<T2>
rather than Extend
or impl Extend
rather than Cast
. (T: Extend<T2>
does not imply that Bundle<U2, T2>
and Bundle<U, T>
have the same representation, which is what impl Cast
is stating.)
|
||
## Downcasting (safe RTTI) | ||
The only item left on the list of requirements for inheritance is the problem of downcasting. To deal with this, | ||
yet another trait is introduced, `Typed`. This trait has two properties that make it unique: |
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.
This is essentially the same thing as the existing Any
trait1.
1 Except that as the current Any
is implemented, it is known to be impl
ed by all types T: 'static
, which is obviously wrong.
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.
obviously
It's not to me, could you explain in a little more detail?
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.
fn evil_id<T: 'static>(mut arg: T) -> T {
if let Some(an_int) = (&mut arg as &mut Any).downcast_mut() {
*an_int = 666i;
}
arg
}
I shouldn't be able to do this. I should have to specify T: Any
. (I should be infer from the absence of an Any
bound that the function won't do something like this. 'static
should only mean 'static
.)
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.
'static
should mean 'static
and impl<T: 'static> Trait for T
should also mean that you always get Trait when you have 'static
- I see no reason why this is at all unreasonable.
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.
I agree with what you wrote, but I was never implying otherwise.
The unreasonable part is having impl<T: 'static> Any for T
in particular. We should have compiler-generated impls for each type individually, but not a blanket impl for all types. The Any
bound should be satisfiable by any concrete type, but not by abstract type parameters except if one explicitly writes T: Any
. (As this RFC also states.)
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.
I'm still unsure why this actually needed.
Wouldn't
impl <T: 'static> Typed for T {
static TYPE_ID: TypeId::of::<Self>()
}
do the same job as an impl for all types? I'm not clear on why it's important that I have to write specifically T: Typed
.
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.
So that contracts are explicit and type signatures actually mean something. If we allow dynamically casting any abstract type to any other type, then a function requiring T: 'static
could do just about literally anything (see above evil_id
). It should be possible to know that a generic function only manipulates / makes use of its type parameters in accordance with the trait bounds which it specifies. I don't know how else to rephrase this to make it clear why this is desirable.
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.
Does this mean you are proposing we should remove Any
?
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.
No, I just think it should be implemented differently. I would prefer (as, again, this RFC we're commenting also proposes) to remove the blanket impl<T: 'static> Any for T
impl (it's actually AnyPrivate
but that's unimportant), and instead, whenever you write:
struct Foo;
the compiler automatically derives:
impl Any for Foo { ... }
How about renaming |
The consensus among the core team is that while this is an orthogonal set of abstractions, in actual use they do not present an ergonomic user experience. |
Add `isEqual` named export to`@ember/utils`
This is largely based upon #9 and #91, but fleshed out to make an actual inheritance proposal.