31 ms·
This is how I see Rust and why I really like it. As a car analogy, basically under the hood it has the C and C++ engine (more true yet using all llvm optimzatio
by fungos 10y ago
This is how I see Rust and why I really like it. As a car analogy, basically under the hood it has the C and C++ engine (more true yet using all llvm optimzations for clang) but it has more modern Teslaish/Ferrarish body. :)
- chubot 10y agoSo do Rust traits do all the stuff that concepts do? Everything is zero-overhead?
- steveklabnik 10y agoTraits can be used in both dynamic and static dispatch contexts: trait Foo { } // pretend this has methods fn static_dispatch<T: Foo>(x: &T) {} fn dynamic_dispatch(x: &Foo) {} One area in which dynamic dispatch works differently in Rust is that &Foo there is a "double pointer": it's a (pointer to data, pointer to vtable), which is different than how C++ does it. The static dispatch code will get monomorphized for each T that it's called with.
- cpeterso 10y agoGiven Rust's focus on zero runtime overhead, I'm surprised that the static dispatch case requires more awkward syntax. People will write code using the simpler, more familiar syntax and incur the dynamic dispatch overhead.
- tatterdemalion 10y agoThere's some nuance but a lot of us regret the dynamic dispatch syntax and wish we had done something like this: fn dynamic_dispatch(x: &virtual Foo) { } fn static_dispatch(x: &Foo) { } // is just syntactic sugar for fn static_disatch<T: Foo>(x: &T) { } As "consolation," dynamic dispatch is usually harder to wrangle with for semantic reasons and so people generally don't use it unless they need to.
- pdpi 10y agoI guess it's a bit a matter of taste, but to me "x: &Foo" seems unambiguously like dynamic dispatch (given that Foo is a trait). The only thing you know about the type of your parameter is that it is an instance of Foo, therefore you need dynamic dispatch, whereas the T: Foo, x: &T reads like "x is of a specific concrete type that's an instance of Foo", which gives you static dispatch.
- tatterdemalion 10y agoI think this is informed by the fact that that's what it means today. If Rust didn't have any dynamic dispatch at all, and it was suggested we have this `&Foo` means `&T: Foo` sugar, I can't imagine anyone saying "That syntax looks like dynamic dispatch!" "What dynamic dispatch? Rust doesn't have that feature." And since users almost never specify the implementing type manually (type inference figures it out with very few exceptions), the whole notion that this is a function parametric over types is obscured for many users. There are trickier issues though about the fact that in return positions we'd want it to mean something different (existential vs universal), and there are "higher order" positions in which its very difficult to determine which of the two semantics you meant here. This has a resemblance to covariance & countervariance.
- pdpi 10y agoActually, I think that it's because of how each syntax maps to other languages I've used before. The parametrically polymorphic version reads a lot like Haskell, and both will monomorphise and use static dispatch: fn doStuff<T: Foo>(x: T) -> T doStuff :: Foo t => t -> t Whereas the Trait Object syntax Reminds me more of Java's syntax, and both use dynamic dispatch: fn doStuff(x: &Foo) public void doSttuf(Foo x) I don't think there's anything intrinsic about either syntax that suggests it _must_ use the dispatch style it does, but when I first started learning Rust, these similarities made it click for me which syntax went with what.
- steveklabnik 10y agoIt's two conflicting desires: should the simple case do the right thing, or should the language be more consistent? You're not wrong, but language design isn't easy!