"new traits can be implemented for existing types (as with Hash above). That means abstractions can be created after-the-fact, and applied to existing libraries." Sounds a bit like a Ruby monkey patch. What happens in case of conflict - my trait adds a .hash method, but there already was one?
Trait methods are only visible when the trait is in scope. So a conflict would only appear when both traits are imported and the method is called, in which case you'll have to disambiguate.
Abstraction without overhead: traits in Rust
141–150 of 150 posts
Re: Abstraction without overhead: traits in Rust
#142Earlier quoted context omitted.
Trait methods are only visible when the trait is in scope. So a conflict would only appear when both traits are imported and the method is called, in which case you'll have to disambiguate.
Cool, thanks for explaining. :) Would it be a compile-time or runtime error?
Re: Abstraction without overhead: traits in Rust
#143Re: Abstraction without overhead: traits in Rust
#144Earlier quoted context omitted.
Extra indirection? There is less indirection. With the C++ approach the data is one pointer away but the vtable is two pointers away. With the Rust approach both data and vtable are just one pointer away.
That's assuming that a "full pointer" (i.e. an unpredictable address to an arbitrary point in heap memory) is the only way to get a reference to an object. What about if you want to stick a bunch of objects in a vector, and iterate over them linearly? In the C++ approach, the data will have a nice linear cache access pattern. In the Rust approach (unless I'm missing something) you'd be storing a vector of (double siz…
No, it's assuming that a "full pointer" is the only way to get a reference to a polymorphic object. This is true in both C++ and Rust.
> What about if you want to stick a bunch of objects in a vector, and iterate over them linearly?
You can do this in both C++ and Rust. But you can only put a bunch of objects in a vector if they have a statically-known type. The point of dynamic dispatch is calling methods of an object where you don't statically know its type.
Re: Abstraction without overhead: traits in Rust
#145Earlier quoted context omitted.
How would you put or index objects in a vector in C++ if they are virtual / dynamically sized? I'm in impression that unless you have the dynamically sized object behind a pointer, you get object slicing.
I didn't say they were dynamically sized. You could either have objects of the same type, but with virtual functions, or you could make a union of all applicable objects (thus guaranteed to be constant sized, at the size of the largest object) and store those in the array/vector, switching on a type enum or calling a function from an inherited base class.
If objects have the same type, which is statically known, there is no need for virtual functions because the compiler can resolve the specific method implementation at compile-time.
> or you could make a union of all applicable objects (thus guaranteed to be constant sized, at the size of the largest object) and store those in the array/vector, switching on a type enum
You can do this in both C++ and Rust easily, with equivalent efficiency in both. In Rust you would just use an enum type. This design is generally highly discouraged though, because it requires callers to be aware of all possible "derived classes".
My comments were only about the case where you are using true language-level polymorphism.
Re: Abstraction without overhead: traits in Rust
#146Earlier quoted context omitted.
>- allows traits to be implemented for existing types, as opposed > to C++ where the type's declaration has to list all base classes. >- allows a type to be used through dynamic dispatch while allowing users > who don't need this to avoid the vtable overhead. In C++ you can have this as well. For example, std::function is able to wrap any callable type without them having to have any base classes. Sean Parent gives a…
This only works for callables, though; if you want a virtual dispatch wrapper for some other set of methods, you have to write it yourself (right?). On the other hand, if you do have a relevant base class and you know the exact type, you can avoid virtual calls in C++ just by declaring the relevant methods final, although the vtable pointer will still bloat the struct a bit. However, I'd say Rust traits are more eleg…
Re: Abstraction without overhead: traits in Rust
#147Earlier quoted context omitted.
Lifetimes aren't regions. 4 years when it is a recapitulation of existing technology and hasn't had time to push anything forward and we already have PLs with similar facilities? That's a product-oriented rather than research-oriented direction whether you think it's too fast or not. For comparison, Haskell dates to the early 90s, based on non-strict FP languages from the 80s. ML itself started in the 70s. Much of wh…
I don't think Rust was ever intended to be primarily a research language. From what I gathered even from early docs (in Graydon's reign), it was intended to implement ideas previously introduced in research languages in a practical systems language. They even made mention of adopting only safe ideas* (and I'm too lazy to look for those quotes). I think they've had to innovate some but that wasn't the primary intent.
Indeed, Graydon chose the name "Rust" specifically because he wanted to avoid cutting-edge language features in favour of old, well-tested ones.
(Reasonable people could disagree whether the 1.0 language meets that goal, but it was a goal.)
Re: Abstraction without overhead: traits in Rust
#148Earlier quoted context omitted.
I'm not exactly sure what they mean, since the following works, and is pretty much a drop-in replacement for a Box trait object in Rust. data Showable = forall a. Show a => Showable a This allows for [Showable 1, Showable "foo", Showable 'x'] (It requires the ExistentialQuantification language feature.)
Right, this is exactly what I meant by setting up the indirection yourself. The main difference seems to be that in Haskell, you have to write this code separately for each typeclass, and wrap/unwrap it manually. In Rust, you just change Show to &Show and you get dynamic dispatch with no extra code. Here's a gist of the two approaches: https://gist.github.com/evanpw/89d89aae1159c608c476 . One other difference is that…
Which seems to me to be effectively the same as `Barrable` in this case, although it's more generic.
Re: Abstraction without overhead: traits in Rust
#149Earlier quoted context omitted.
Right, this is exactly what I meant by setting up the indirection yourself. The main difference seems to be that in Haskell, you have to write this code separately for each typeclass, and wrap/unwrap it manually. In Rust, you just change Show to &Show and you get dynamic dispatch with no extra code. Here's a gist of the two approaches: https://gist.github.com/evanpw/89d89aae1159c608c476 . One other difference is that…
This works on single instances, but I'm trying to see how it plays out with lists of `Bar`. As far as I can see, you need to use `Box`: https://play.rust-lang.org/?code=struct%20Foo%3B%0Astruct%20... Which seems to me to be effectively the same as `Barrable` in this case, although it's more generic.
Which is exactly what you said in the first place!
> So you can have a list of Box in Rust, but you can't have a list of Show in Haskell.
But isn't this comparing two different things? You can't have a list of trait/typeclass in either language (excuse the pseudo-syntax):
Rust: [Bar]
Haskell: [Bar a => a]
But you can (with some extensions in Haskell) have: Rust: [Box]
Haskell: [Box Bar]Re: Abstraction without overhead: traits in Rust
#150Earlier quoted context omitted.
This works on single instances, but I'm trying to see how it plays out with lists of `Bar`. As far as I can see, you need to use `Box`: https://play.rust-lang.org/?code=struct%20Foo%3B%0Astruct%20... Which seems to me to be effectively the same as `Barrable` in this case, although it's more generic.
> As far as I can see, you need to use `Box` Which is exactly what you said in the first place! > So you can have a list of Box in Rust, but you can't have a list of Show in Haskell. But isn't this comparing two different things? You can't have a list of trait/typeclass in either language (excuse the pseudo-syntax): Rust: [Bar] Haskell: [Bar a => a] But you can (with some extensions in Haskell) have: Rust: [Box ] Has…
The original comment I replied to said that trait objects (e.g., &Bar) are implemented in the same way as typeclass instances. At first glance, that seems true: both pass around a vtable and determine which function to call at runtime. However, Rust attaches the vtable to the trait object pointer itself, while Haskell passes the vtable (method dictionary) around as a hidden argument. That seems like a trivial implementation detail, but it has an effect on the language: in Rust you can create a list of trait objects, while in Haskell, you can't have a list of typeclass instances.
It's true that you can emulate the Rust implementation in Haskell: under the hood, the existentially quantified Barrable contains a pointer to an instance of Bar as well as the dictionary of Bar methods, exactly like Rust's fat pointer representation of a trait object. But since the rest of the language expects method dictionaries to be passed as separate arguments, you have to wrap and unwrap values of type Barrable in order to use them.
This is a great example of how implementation choices influence language design, which is a topic I find really fascinating.