Live data from Hacker News

Abstraction without overhead: traits in Rust

blog.rust-lang.org

91–100 of 150 posts

Re: Abstraction without overhead: traits in Rust

#91
post #90

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…

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.

That would explain a lot. How do the goals contrast with C++ then? Smaller language?

Re: Abstraction without overhead: traits in Rust

#92
post #90

Earlier quoted context omitted.

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.

That would explain a lot. How do the goals contrast with C++ then? Smaller language?

For reasons of backwards-compatibility, it's basically impossible to design extensions to C++ that make the language memory-safe by default. C++11/14 are admirable best-effort approaches to doing so, but they provide only tools for helping to enforce memory safety without providing any actionable guarantees. Rust guarantees memory safety, with the only possibility of unsafety being relegated to blocks of code specifically denoted as `unsafe`.

In addition to memory safety, Rust's other goal was to improve the ability of programmers to reason about low-level concurrency, motivated by the enormous pain that both the Firefox and Chrome developers are currently experiencing by trying to adapt their browsers to a multicore world. The serendipitous discovery was that the same mechanism used to guarantee memory safety will also statically guarantee that your program is free of data races.

TL;DR: Rust's goals are guaranteed memory safety, guaranteed freedom from data races, and zero runtime overhead relative to C++.

Re: Abstraction without overhead: traits in Rust

#93
post #7
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…

It also makes multiple inheritance (which Rust has, through Java-like interfaces) easy to implement, and fast at runtime. The virtual inheritance of C++ is a real mess, by contrast [1]. [1]: http://www.phpcompiler.org/articles/virtualinheritance.html Edit: I don't mean to bash C++ here, BTW; the skinny-pointer approach has a lot of benefits when all you need is single inheritance (and there are early-stage proposals…

What is the representation for a type like Box? Is there an extra indirection to get each of the vtables? Is there synthesized combined trait type and just one vtable pointer?

Re: Abstraction without overhead: traits in Rust

#94

Earlier quoted context omitted.

I think he means that, through templates, the C++ type system is Turing-complete.

Rust's is too.

Though the skimpy recursion limit makes it fairly impossible to do anything interesting with this property, much to the chagrin of some people. :P

Re: Abstraction without overhead: traits in Rust

#95
post #93
post #7

Earlier quoted context omitted.

It also makes multiple inheritance (which Rust has, through Java-like interfaces) easy to implement, and fast at runtime. The virtual inheritance of C++ is a real mess, by contrast [1]. [1]: http://www.phpcompiler.org/articles/virtualinheritance.html Edit: I don't mean to bash C++ here, BTW; the skinny-pointer approach has a lot of benefits when all you need is single inheritance (and there are early-stage proposals…

What is the representation for a type like Box ? Is there an extra indirection to get each of the vtables? Is there synthesized combined trait type and just one vtable pointer?

I am pretty sure you can't actually construct such a type (at least not at the moment) so Rust sort of sidesteps the question.

Re: Abstraction without overhead: traits in Rust

#96
post #92

Earlier quoted context omitted.

That would explain a lot. How do the goals contrast with C++ then? Smaller language?

For reasons of backwards-compatibility, it's basically impossible to design extensions to C++ that make the language memory-safe by default. C++11/14 are admirable best-effort approaches to doing so, but they provide only tools for helping to enforce memory safety without providing any actionable guarantees. Rust guarantees memory safety, with the only possibility of unsafety being relegated to blocks of code specifi…

kibwen, this whole reply should be an FAQ somewhere if it isn't already. Nice precision!

Re: Abstraction without overhead: traits in Rust

#97
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'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.

Because they are enums done right. Enums in C and many other languages are often just a helpers to define integer constants (or abused in this way) and can be evil.

Re: Abstraction without overhead: traits in Rust

#98
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.

C++ traits, templates, and non-virtual classes have no overhead.

Re: Abstraction without overhead: traits in Rust

#99
> What you do use, you couldn’t hand code any better.

... given you have no information about runtime behavior other than the static code.

How to achieve "zero-cost abstractions" is, as usual, a design tradeoff.

Rust -- like C++ -- lets you choose in your code whether you'd like to pay for an abstraction or not. This does produce good machine code, but has two costs: 1/ the language gets more complicated and the programmer needs to be aware of what she's paying for, and 2/ you might end up paying for stuff you end up not using (for example, all button listeners might end up being the same type, and a vtable adds unnecessary cost), so that the generated code is only optimal if you have no other information about runtime behavior.

There's another way to add zero-cost abstractions: have a single simple abstraction and a JIT to figure out at runtime -- based on observed behavior -- what the optimal machine code is. This is what the JVM does. All method calls are virtual from the programmer's perspective, and no choice needs to be made ahead of time. At runtime, the JIT views the class hierarchy and usage, and decides whether a specialized, inlined version of a function is produced or whether a vtable is actually necessary (HotSpot even makes a special case when there are exactly two implementations, replacing the vtable with an `if`). If runtime behavior changes (a new type of listener is added or even new code is loaded at runtime that adds another implementation), the JIT will notice, reconsider and recompile. So in Java, virtual or even interface method calls are also zero-cost, even though you have no choice about using them; the decision on how to implement the abstraction is done by the (JIT) compiler at runtime. This, too, is a tradeoff -- a simpler language and truly optimal code taking into consideration not only static consideration but actual runtime behavior -- at the cost of a possibly significant warmup time and possible non-optimal "mistakes" by the JIT.

Re: Abstraction without overhead: traits in Rust

#100
post #42

Earlier quoted context omitted.

And typeclasses in Haskell as well.

I don't think that's true. Dictionary-passing (or something equivalent) attaches the table of methods to the functions which take typeclass instances as arguments (with extra, hidden arguments), while Rust's Box attaches the table of methods to the object pointer itself. So you can have a list of Box in Rust, but you can't have a list of Show in Haskell. You have to set up the extra indirection yourself using somethi…

> but you can't have a list of Show in Haskell

I'm not sure exactly what you mean, but my naive interpretation is that you can:

    Prelude> :t map show
    map show :: Show a => [a] -> [String]
Post reply on HN