Hacker News new | ask | show | jobs
by tialaramex 1712 days ago
And clever use of blanket implementations makes traits even nicer for this purpose.

For example, suppose you just invented the type Qux and you always know how turn any Baz into a Qux.

Rust's standard library provides four interesting conversion traits that may be applicable for different purposes, From, Into, TryFrom, and TryInto. Oh no, implementing all of them sounds like a lot of work and who knows which anybody would need?

Just implement From<Baz> for Qux

A blanket implemention of Into<U> for T when From<T> for U gets you Into, then a blanket implementation of TryFrom<T> for U when Into<U> for T gives you TryFrom, and finally a blanket implementation of TryInto<U> for T when TryFrom<T> for U gets you TryInto as well.

So you only wrote one implementation but anybody who needs any of these four conversions gets the one they needed.

Another more obvious example is the blanket implementation of IntoIterator for any Iterator. This makes it easy when you want to be passed a bunch of Stuff you're going to iterate over, just give a trait bound on your parameter saying it has to be IntoIterator for Stuff. You needn't care if the user of your function passes you an array, a container type, or an iterator, since all of them are IntoIterator.

It would have been so easy to overlook this, and end up with a language where people find themselves collecting up iterators into containers over and over to make more iterators, or else constantly turning containers into iterators to call functions.

1 comments

That's just enable_if from C++, not some amazing new Rust invention.
First up let's be absolutely clear - there aren't many (any?) "amazing new Rust inventions". Rust isn't a proof of concept, it's the mass market product and so it just stole clever ideas wholesale from languages we don't care about, and in some cases haven't even heard of. The various papers saying C++ should do things that Rust did aren't because Rust invented those things but because it has popularized them and now people writing C++ papers want them.

It is true that SFINAE means you can write very general templates in C++ and just not worry about whether they compile with any particular parameters, which is superficially similar to a blanket implementation of a Rust trait. But, because Rust's traits have semantics not just syntax the actual effect is different.

And that makes the blanket implementations very comfortable in Rust, whereas the equivalent pile of enable_if templates in C++ would be trouble.

Remember C++ comes with template language that is explicitly labelled as having unenforced semantic value. It's for documentation, a human reading them can see for example that you expect this parameter type to have full equivalence. However the compiler only cares about syntax, and the syntax just says the type has an equals operator.

This is a great contrast with Rust, whose traits have semantics and so core::cmp::Eq and core::cmp::PartialEq are different traits and the compiler cares which one you asked for even though the syntax is the same.

> But, because Rust's traits have semantics not just syntax the actual effect is different.

Don't concepts give C++ the same semantic power? What am I missing? Concepts give C++ generic code exactly the same nominative constraints you're describing, yes?

Besides: even without concepts, you can use tag types and traits to similar effect. Not everything needs to be duck-typed, even in C++17!

One of my main problems with using Rust instead of C++ is losing metaprogramming power. I really like the template system.

> Don't concepts give C++ the same semantic power?

Not really. But, it's on purpose, and, in their context it makes sense. These aren't idiots, they know there are however many bajillion lines of C++ code and if Concepts doesn't work with that code it's useless.

In Rust, you have to explicitly write implementations of traits. If the author of stupid::Bicycle doesn't implement nasty::Smells and the author of nasty::Smells didn't implement it for stupid::Bicycle either, then a stupid::Bicycle just isn't nasty::Smells.

Even "marker traits" where the implementation is empty, are still explicit. If you have a type that implements Clone but you didn't say it is Copy, then it isn't Copy, even though maybe the type would qualify as Copy if you had asked. Rust will always move this type, because you didn't say copying it was OK.

This is where the semantic strength comes from, and it was fine in Rust because Rust "always" did this. There isn't a bajillion lines of Rust code that doesn't know about traits, and so it's OK to require this.

In C++ they realistically couldn't do that. So, next best thing is to say that a Concept is modelled on syntax. You mentioned duck-typing, and that's what concepts are assumed to be for.

Now, of course you can use tag types, for a home grown Concept it actually might be viable, if you've only got two users and they both know you personally you can just tell them to use the tag type and they'll both add it. But for the C++ standard library Concepts that was not practical. So, that's not how Concepts is being taught or has been used in practice, unlike Rust's traits.