4 ms·
> Rust has a perfectly nice type system by modern standards I disagree -- Rust's type system is pretty weak and very limiting compared to other modern language
by elcritch 4y ago
> Rust has a perfectly nice type system by modern standards
I disagree -- Rust's type system is pretty weak and very limiting compared to other modern languages like Typescript, Nim, Zig, or even C++. That's without even without getting into dependent type languages.
There are so many basic patterns which Rust's type system can't handle, especially when it comes to compile time types. For example, one recent thing I ran into was trying to downcast a dyn Trait into another more specific dyn Trait. It's just not possible, at least on stable. Instead you have to do ugly work like getters that return sub-traits wrapped in optionals. Ick, the visitor pattern was easier.
Rust's type system philosophically takes a very strict "closed system" approach too. Meaning one can only really write code that's valid for every known instance. This limits entirely valid and useful subset of programs to programmers using libraries or language features. I'm not talking about runtime dynamism, but compile time dynanism. C++ with concepts provides a much more powerful and adaptive type system.
Programming in Rust frustrates me that I can't do any of the compile time checks that I've become accustomed to doing in other languages. It makes doing them at runtime difficult as well.
> but it's nowhere close to showing you just how deep the rabbit hole goes when it comes to avoiding bugs at runtime by having stronger type systems.
Totally agree on that! Though that's missing how powerful compile time programming in general can be even without the dependent types. Personally I'm excited to see how C++ Concepts can evolve. Though I'm not sure how much that starts overlapping with Dependent Types.
- tialaramex 4y ago> Personally I'm excited to see how C++ Concepts can evolve. So, C++ 20 Concepts is basically what Bjarne Stroustrup proposed for a future version of his C++ language in the early 2000s. Several people proposed and WG21 accepted, a far more capable feature set for Concepts, this is often referred to as C++ 0x Concepts, since it was accepted for C++ 0x, the standard that would eventually (after years of delays) become C++ 11. Bjarne wrote a paper arguing that this more powerful feature set was unnecessary and perhaps unworkable, and WG21 wound up removing Concepts from C++ 11 entirely (and the people behind it mostly got the message and ceased working on C++ altogether). A decade later, something that's close to Bjarne's original proposal became a C++ 20 feature. C++ 0x Concepts was similar to Rust's Trait system in many ways. Particularly notable features of C++ 0x Concepts you might recognise in Rust's Traits: 1. Third parties can implement a C++ 0x Concept for some type which was not originally conceived with this Concept in mind, they just write the implementation and it works. 2. C++ 0x Concepts must be explicitly implemented they're not just a syntactic requirement that could be satisfied by happenstance in a type which is not in fact suitable. 3. As a result of 1 & 2, the C++ 0x Concepts have Semantics which in C++ 20 Concepts are confined to the idea of "modelling" a Concept or else IFNDR.
- elcritch 4y agoTo be fair I've mostly used concepts in Nim, not C++ x0 concepts. However, your comparisons of C++ concepts to Rust traits seems to be lacking a lot of details or is plain inaccurate. It also misses the flexibility of C++ concepts. > C++ 0x Concepts was similar to Rust's Trait system in many ways. Particularly notable features of C++ 0x Concepts you might recognise in Rust's traits: Perhaps at the loosest level of comparison around only defining limitations on possible types. However, C++ x0 concepts enable much more powerful combinations of logic to specify if a template fulfills a concept. In Nim the concept can be any arbitrary boolean statement. This blog has some good examples of C++ concepts: https://www.sandordargo.com/blog/2021/03/10/write-your-own-cpp-concepts-part-i https://www.sandordargo.com/blog/2021/03/10/write-your-own-c... It's trivial to specify that a C++ concept can be either a float or an integer: template <typename Base, typename Exponent> concept HasPower = (std::integral<Exponent> || std::floating_point<Exponent>) && requires (Base base, Exponent exponent) { base.power(exponent); }; That's not possible AFAICT with Rust's traits. > 1. Third parties can implement a C++ 0x Concept for some type which was not originally conceived with this Concept in mind, they just write the implementation and it works. That's true for C++ concepts, but not entirely true for Rust traits. You can only implement a Rust trait if you own the type or own the trait. If you use two third party libraries, you cannot implement a trait from one for a type from the other. At best you can wrap the type in a new struct, and reimplement the parent's traits. https://stackoverflow.com/questions/25413201/how-do-i-implement-a-trait-i-dont-own-for-a-type-i-dont-own https://stackoverflow.com/questions/25413201/how-do-i-implem... > 2. C++ 0x Concepts must be explicitly implemented they're not just a syntactic requirement that could be satisfied by happenstance in a type which is not in fact suitable. That doesn't appear to match with C++ resources like: https://en.cppreference.com/w/cpp/language/constraints https://en.cppreference.com/w/cpp/language/constraints Everything in Rust traits requires them to be encoded into existing traits. C++ concepts let you define rules for arbitrary combinations of types. So you can create functions that take two independent types and define a constraint on those types.
- tialaramex 4y agoUnfortunately you don't seem to have understood much of what I wrote. The most crucial thing to understand is that C++ 0x Concepts were a significantly more powerful feature than the C++ 20 Concepts you got. Even though I emphasised this, you seem to have muddled them together to produce what you're calling "C++ x0 concepts" in several places, which is not actually a thing. > That's not possible AFAICT with Rust's traits. It is of course possible to write a Rust trait for something as useless as "any numeric type regardless of what kind", that's what the Num crate's Num trait is. Because Rust's traits have semantics, useless traits reveal themselves - you can't do much with something whose only decisive property is that it's numeric in some way. Num also defines some traits for numeric properties that are way more useful, like the additive and multiplicative identities (Zero and One). A type can be numeric without having zero (NonZeroU32 is a trivial Rust example from the standard library) so expressing that you mean specifically a type with additive identity is useful in a way that merely "numeric" largely is not. The "HasPower" example is revealing though, lots of people's toy Concepts are like this. They just dictate a morsel of syntax. C++ 20 Concepts are indeed suitable for this, but so is nothing whatsoever, because of C++ template "magic". Why C++ 20 Concepts at all then? Your C++ compiler's diagnostics with nothing whatsoever are terrible because Substitution Failure Is Not An Error. Bjarne's simple "Concepts" can hide this somewhat - the diagnostics you get for a Concept failure are more digestible. > That's true for C++ concepts, but not entirely true for Rust traits. No, it would be true for C++ 0x Concepts but those never existed beyond a draft document. It does work for Rust traits as you say but you can't do it for C++ 20 Concepts. > That doesn't appear to match with C++ resources like: https://en.cppreference.com/w/cpp/language/constraints https://en.cppreference.com/w/cpp/language/constraints Once again you're confused, that document is about C++ 20 Concepts, which exist, but I was describing C++ 0x Concepts, which are much closer to the capabilities of Rust's Traits and were never implemented. > Everything in Rust traits requires them to be encoded into existing traits. What you've written is a tautology. So I can only guess what insight you thought you had here. Maybe you're imagining it's not possible to do obvious stuff like say that a type T must implement both trait A and trait B (a conjunction, signified in C++ Concepts with &&)? I assure you things do that all the time in Rust. foo<T: A + B + C, S: C + D>(p1: T, p2: S) is a function which takes two parameters p1 and p2, the type of p1 must implement traits A, B and C, while the type of p2 must implement traits C and D. For such complicated trait bounds idiomatic Rust would use the where keyword, but it's not mandatory, just easier to read. In Rust you can't write the disjunctive bounds C++ 20 Concepts can express as || because it's not yet clear (and might never become clear) how to do so in a sound way. C++ doesn't care, none of the rest of the language is sound anyway, so it's too late to worry.