Hacker News new | past | comments | ask | show | jobs | submit login

Unfortunately 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

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.




> The most crucial thing to understand is that C++ 0x Concepts were a significantly more powerful feature than the C++ 20 Concepts you got.

This makes a bit more sense than what you originally wrote.

Though regardless if C++ 0x concepts were more powerful, C++ 20 concepts as they exist today are strictly more powerful than Rust traits. You admit that yourself when you say that Rust traits cannot model disjunctive bounds. They certainly can't do arbitrary boolean predicates or negation.

That means that Rust's trait system cannot implement compile time type checking rules that C++ 20 concepts can today. You cannot encode entire sets of logic in the type system that you can in encode in C++ (or other) languages.

Features like disjunctive logic may not be able to be proved "sound" for things like borrow checking but that's not the point or intent. The point is that you can use arbitrary boolean predicates in inventive ways. Hence my original comment about "seeing where C++ concepts go".

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

True, my terminology got muddled. Trying to follow your terminology and (odd) backstory about C++ 0x concepts was confusing.

You are incorrect about how C++ 20 concepts work and that makes it more confusing.

There are lots of resources showing that C++ concepts are implicit and don't need to be explicitly instantiated. Take this Rust comparison: https://mcla.ug/blog/cpp20-concepts-are-not-like-rust-traits...

While Rust traits force a "closed" system (to be imprecise) that's easier to prove soundness on upfront, that doesn't make the type system more powerful. It may make it more useful in some people's view. That's a pretty big distinction.

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

Err, no that's incorrect as you can easily check in any of the references I gave. C++ concepts as they exist allow you to call concepts if they fulfill the concept.

In Rust you must own either the trait or the type in order to implement said trait for that type. This is a widely known and deliberate limitation of the Rust trait system. It has some benefits, but is also leads to significant "trait bloat".

The "orphan rule" doesn't exist in C++ 20 concepts. It's an intentional Rust design decision: https://rust-lang.github.io/chalk/book/clauses/coherence.htm...

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

I don't really follow what you're trying to say here.

In contrast, I do find it very useful to define default algorithms for any numeric type that matches. It's a core part of C++ numerical libraries.

However, I get that this would be fairly pointless in Rust because you can't do much useful with it without things like generics specializations being stable.

> 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".

This makes no sense. A "morsel of syntax" or alluding to "C++ template magic" make no sense.

Granted C++ template's are amazing powerful, and amazingly difficult to debug.

C++ 20 concepts provide a useful and flexible way to describe compile time type restrictions, while not limiting C++ templates to the purely adjunctive subset of type logic able to be described in Rust's trait system.

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

It is, I was being lazy but the point I'm reaching for is that using Rust's trait system requires the traits you want to target to already exist and to be implemented for the types in question. Moreover, creating some type-based logic rules using the Rust trait system, requires that logic to effectively already be encoded into the traits (as some combination of adjunctive properties).

This is why you end up with incompatible HAL libraries for various STM32 models, among others. In my opinion it's a very limiting part of the ecosystem.


> This makes a bit more sense than what you originally wrote.

It re-states what I originally wrote, I'm glad you find it clearer now although since this is a matter of history it was all there for you to read if you cared. I don't think I have time to say everything two or three times until you "get" it.

> Features like disjunctive logic may not be able to be proved "sound" for things like borrow checking but that's not the point or intent.

The soundness problem is, unfortunately, fundamental. It's not about the borrow checker. C++ doesn't care whether your program has any logical meaning at all, so long as it is syntactically OK in these cases - of course in such case its meaning is unknown, but the standard explicitly tells compilers not to worry about that, the important thing is that the gibberish compiled, a C++ programmer can congratulate themselves on another successful project.

Maybe I need to expand the abbreviation I used, IFNDR: Ill-Formed, No Diagnostic Required. This is what the standard says to wave away such problems, not only with concepts but throughout the language. "Ill-formed" means this isn't actually a C++ program and so the standard does not define what it means, but "No Diagnostic Required" means the compiler needn't give an error or warning, it just presses on anyway.

[ You might imagine surely they could give a diagnostic, but actually they can't because of Rice's theorem. For a sufficiently powerful programming language you have to pick: 1. Your compiler sometimes gets "stuck" forever trying to decide whether a program is valid. 2. Your compiler reports errors in some otherwise valid programs. 3. Your compiler reports no errors in some invalid programs. Rust chose (2) and C++ chose (3) ]

> C++ concepts as they exist allow you to call concepts if they fulfill the concept.

Once again you've got turned around. The question isn't whether you can call concepts but whether anybody else can implement the concepts, and you simply can't do that. C++ 0x Concepts had "concept maps" to fix this, in Rust obviously the traits are explicitly implemented, but C++ 20 Concepts doesn't have an equivalent.

> Granted C++ template's are amazing powerful, and amazingly difficult to debug.

They're copy-paste. A slight improvement on C pre-processor macros. I suppose it's in the name, "templates" like a mail merge system. It's childishly simple like the cups and balls trick. The resulting mess does indeed produce unintelligible error messages and is also unsound in both obvious and surprising ways.

> In contrast, I do find it very useful to define default algorithms for any numeric type that matches. It's a core part of C++ numerical libraries.

Useful here meaning only you get better error messages than from SFINAE?

> using Rust's trait system requires the traits you want to target to already exist and to be implemented for the types in question.

I think it obviously follows that you can't use things which don't exist.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: