Live data from Hacker News

Abstraction without overhead: traits in Rust

blog.rust-lang.org

71–80 of 150 posts

Re: Abstraction without overhead: traits in Rust

#71
post #9
post #5

Great article. The main bit of new information for me was that while Rust supports dynamic dispatch, its implementation has a noticeable difference from C++. In C++, the vtable pointer is in the object itself. In Rust, it's stored inside what is essentially a "fat pointer." Pointers to traits ("trait objects") are actually two pointers: the pointer to the vtable and the pointer to the actual object. This seems to hav…

This is also how Go does it. It looks like Rust traits can do everything Go interfaces can do, and more?

There are some key differences, like Rust traits using generics, and Go interfaces using an empty interface for generic code.

Re: Abstraction without overhead: traits in Rust

#72
post #21
post #5

Great article. The main bit of new information for me was that while Rust supports dynamic dispatch, its implementation has a noticeable difference from C++. In C++, the vtable pointer is in the object itself. In Rust, it's stored inside what is essentially a "fat pointer." Pointers to traits ("trait objects") are actually two pointers: the pointer to the vtable and the pointer to the actual object. This seems to hav…

>- 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 elegant in unifying what are two separate worlds in C++:

(1) calling virtual methods on a base class pointer, which can be (usually is) dispatched at runtime, but thus can't support methods with generic parameters or having a 'virtual type' (instead of a method), and

(2) accessing members of template parameter classes, including (possibly generic) methods, constants, typedefs, etc. - much more flexible, but doesn't work at runtime. Also dynamically typed, for better or worse; Rust thinks worse.

In Rust, (1) is a trait object, and (2) can be done with a generic parameter specified to implement a certain trait with methods, associated types, and constants. Rust's compiler doesn't try to be magic (unlike, say, Haskell), and so traits with generic methods and such can't be made into trait objects, but they're still traits - they feel like the same basic kind of thing.

Re: Abstraction without overhead: traits in Rust

#73
post #29

Earlier quoted context omitted.

The turing-complete argument makes no sense here.

Sure it does. I can pass around a struct containing any bytes I want and do anything I want with it. If I want to define FatPointer that contains an opaque pointer (or some equivalent union) and a polymorphic Strategy object of some sort, I can. You can do the same in C, for that matter. Or in assembly. The reason we still come up with new languages is so that you can write code that conforms to certain patterns easi…

No, Turing-completeness does not mean you can "do whatever you want". It means you can compute whatever you want that is computable. If the way you want to compute things is to embed a Rust compiler, yes you can do that, but there are many things that we'd colloquially refer to as "do whatever you want" that apply to the semantics of C++ itself.

You cannot, for instance, create a new abstract base class and have a pre-existing class in someone else's header implement your abstract base class, such that a pointer to that pre-existing class can be dynamic_cast to your class.

Because that statement refers to C++ semantics, not your ability to compute things, it's not affected by the claim of Turing-completeness.

Re: Abstraction without overhead: traits in Rust

#74
post #6
post #2

There is always overhead when adding abstractions--the only question is whether you pay at runtime or at compile time. C++ (and presumably Rust) choose the latter, Python and Go choose the former.

If you want to be really pedantic, there is also the choice between "runtime overhead" and "code overhead" (code specialization). And why not mention the overhead of the programmer foregoing these "short cuts" and hand-coding it herself.

That first one ain't pedantic; C++ code on the whole tends to generate a lot of assembly bloat with inlined methods and template instantiations, and while executable size probably isn't that important for most use cases, it is for quite a few.

Re: Abstraction without overhead: traits in Rust

#75
post #57

Earlier quoted context omitted.

I'll have to admit enums are where I gave up the first time I read the Rust tutorial. They seem almost completely unrelated to enums in C and other languages.

Right, because they're unions. :) Just go back to that tutorial with your C hat on and substitute the word "union" for "enum" and I promise it will all make sense. All your intuition about C unions will cross over just fine, and the new Rust rules (they're tagged at runtime and the compiler enforces that you can only ever use fields of a runtype-checked subtype) are straightforward extensions. Likewise the linked blo…

> Just go back to that tutorial with your C hat on and substitute the word "union" for "enum" and I promise it will all make sense. All your intuition about C unions will cross over just fine, and the new Rust rules (they're tagged at runtime and the compiler enforces that you can only ever use fields of a runtype-checked subtype) are straightforward extensions.

This is not true. Rust enums allow you to match on which of the types you have. C unions do not. If you want to implement a switch statement over the possible members of a C union, you need to put it inside a struct with a type field. You don't need to do so in Rust, and you can't do so and have it compile.

(That said, if your real complaint is that the official docs on enums are confusing, I'd certainly agree with that.)

Re: Abstraction without overhead: traits in Rust

#76
post #35

Earlier quoted context omitted.

Stop it, they're unions. :) Among the target market, people are going to intimately familiar with the use of "enum" and "union" from the C language. Rust's concept of a single object that can store exactly one of several types of sub-objects matches one quite closely, and not the other. Having a runtime tag and affirmative checking doesn't change the nature of the thing. We don't call "cars" something different when…

I don't really want to argue about enum vs. union, because as far as I'm concerned they're pretty much equally accurate or inaccurate. Enums in C carry tags, but no data. Unions in C carry data, but no tags. Rust ADTs carry both (or one, or neither). So "enum" or "union" are pretty much equally good/bad names as far as I'm concerned. Consensus was in favor of "enum", so we went with it. Swift did too, so the small am…

> And as a type, "block" sounds like a code block—i.e. something like a lambda. Ruby uses "block" for this, for instance.

As does, notably, Objective-C, like Ruby due to the Smalltalk heritage.

I've never heard the term "block" used to refer to heap allocations, so I don't think it would really help.

Re: Abstraction without overhead: traits in Rust

#77
post #75
post #57

Earlier quoted context omitted.

Right, because they're unions. :) Just go back to that tutorial with your C hat on and substitute the word "union" for "enum" and I promise it will all make sense. All your intuition about C unions will cross over just fine, and the new Rust rules (they're tagged at runtime and the compiler enforces that you can only ever use fields of a runtype-checked subtype) are straightforward extensions. Likewise the linked blo…

> Just go back to that tutorial with your C hat on and substitute the word "union" for "enum" and I promise it will all make sense. All your intuition about C unions will cross over just fine, and the new Rust rules (they're tagged at runtime and the compiler enforces that you can only ever use fields of a runtype-checked subtype) are straightforward extensions. This is not true. Rust enums allow you to match on whic…

> (That said, if your real complaint is that the official docs on enums are confusing, I'd certainly agree with that.)

I'd love more specifics about which docs, if you have some time.

Re: Abstraction without overhead: traits in Rust

#78
post #74
post #6

Earlier quoted context omitted.

If you want to be really pedantic, there is also the choice between "runtime overhead" and "code overhead" (code specialization). And why not mention the overhead of the programmer foregoing these "short cuts" and hand-coding it herself.

That first one ain't pedantic; C++ code on the whole tends to generate a lot of assembly bloat with inlined methods and template instantiations, and while executable size probably isn't that important for most use cases, it is for quite a few.

It is pedantic in the sense that "overhead" in a programming context will most of the time mean some kind of runtime overhead, unless noted otherwise.

Re: Abstraction without overhead: traits in Rust

#79
post #63

Earlier quoted context omitted.

> Wish regions hadn't been abandoned, but it seems like > somebody wanted Rust pushed into becoming a product quickly. No idea what you're referring to here. Lifetimes are entirely based on the regions literature, and four frigging years of design iteration is hardly "pushed into becoming a product quickly". :P

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…

Actually, lifetimes are basically the same as regions, going back to FX87. Lifetimes in Rust are more like regions in Tofte & Talpin 94 than those in FX87, but where inter-region pointers are constrained by the lifetime of regions rather than changing the lifetimes of the regions (as required by Rust's goals for regions).

Re: Abstraction without overhead: traits in Rust

#80
post #79

Earlier 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…

Actually, lifetimes are basically the same as regions, going back to FX87. Lifetimes in Rust are more like regions in Tofte & Talpin 94 than those in FX87, but where inter-region pointers are constrained by the lifetime of regions rather than changing the lifetimes of the regions (as required by Rust's goals for regions).

Rust's goals for linear typing and regions changed. Ownership types didn't happen until typestate was abandoned. The current design is quite different from what was being explored before. If they're happy with what they've got, more power to them, but the project's priorities have changed dramatically in the last 2 years. I think it's worth asking why something that started seemingly as a research language backed off the original work and was converted into a relatively safe technology product in a short span of time.

I find it profoundly disquieting when redactions are created around the history of projects, pushing the impression that the place arrived at was what was wanted all along. Our failures can inform posterity as much as our successes.[1] The core Rust team has been perfectly candid about the history of the project.

[1]: Cf. pre-Monad IO subsystems for Haskell, http://www.scs.stanford.edu/~dbg/readings/haskell-history.pd... and http://research.microsoft.com/en-us/um/people/simonpj/papers...

Post reply on HN