67

In Rust, the main tool for abstraction are traits. In C++, there are two tools for abstractions: abstract classes and templates. To get rid of some of the disadvantages of using templates (e.g. hard to read error messages), C++ introduced concepts which are "named sets of requirements".

Both features seem to be fairly similar:

  • Defining a trait/concept is done by listing requirements.
  • Both can be used to bound/restrict generic/template type parameters.
  • Rust traits and C++ templates with concepts are both monomorphized (I know Rust traits can also be used with dynamic dispatch, but that's a different story).

But from what I understand, there are also notable differences. For example, C++'s concepts seem to define a set of expressions that have to be valid instead of listing function signatures. But there is a lot of different and confusing information out there (maybe because concepts only land in C++20?). That's why I'd like to know: what exactly are the differences between and the similarities of C++'s concepts and Rust's traits?

Are there features that are only offered by either concepts or traits? E.g. what about Rust's associated types and consts? Or bounding a type by multiple traits/concepts?

Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
  • 12
    To acknowledge this up front: I know this question is in danger of being closed as "too broad". And if the vast majority of you think so, well so be it. *But*: I think the question is still fine for this site (it's a fairly specific question about two features) *and* I don't think it's useful to break this question into multiple sub-questions. – Lukas Kalbertodt May 08 '19 at 17:09
  • 1
    To those casting "too broad" CVs in record time: could someone elaborate how you think this question could be asked in a better way? Some tips on what you would accept as a good question? – Lukas Kalbertodt May 08 '19 at 17:23
  • 3
    I was one of them and I think this kind of question is just not really constructive, cannot be answered properly and in general it does not fit SO's format very well. I'd like to see a detailed comparison of the above mentioned features of the two languages -- as an article somewhere perhaps, but not as a short answer. I think @Shepmaster's [link](https://meta.stackoverflow.com/questions/251328) sums this up pretty well – Peter Varo May 08 '19 at 17:26
  • I voted to close because I don't think that questions like "why is feature X in language Y like/different from feature A in language B" are good or worthwhile questions. We have different languages because different people have or like different ways of thinking about things. –  May 08 '19 at 17:28
  • 2
    Thanks for your answers! @NeilButterworth Note that I didn't ask *why* they are different, though. I just asked for the factual differences. – Lukas Kalbertodt May 08 '19 at 17:29
  • 4
    *how you think this question could be asked in a better way* — right now, it appears that any answerer would have to be quite expert in Rust and C++. For example, you off-handedly mention "Rust's associated types and consts", but a C++ expert might not know all the details of such. This means that the pool of qualified people will be *very* small. It's possible that if you describe all the specifics of the technology you know, the question may be easier. – Shepmaster May 08 '19 at 17:29
  • Random responses from the web: [HN](https://news.ycombinator.com/item?id=9275597); [Reddit](https://www.reddit.com/r/rust/comments/9jtzir/c_gets_concepts_aka_traits/); [URLO](https://users.rust-lang.org/t/how-to-design-good-concepts-and-use-them-well-c/9136?u=shepmaster) – Shepmaster May 08 '19 at 17:37
  • 10
    As a relevant parallel question, [What is the difference between traits in Rust and typeclasses in Haskell?](https://stackoverflow.com/q/28123453/155423) has a score of 130 at the moment and is relatively well viewed. – Shepmaster May 08 '19 at 17:43
  • 1
    @Shepmaster I think questions with a very small pool of people qualified to answer are probably the ones most in need of being asked and answered on a site like this. – Kyle Strand May 08 '19 at 20:47
  • @KyleStrand note that I don’t say *anything* about if the question is on topic or not. In fact, in another comment I provide evidence of similar questions that are apparently on topic and well-received. I also didn’t vote to close. OP **asked** for ways to improve the question and that’s what that comment is about. My own interactions with Lukas have shown me that they know about Rust; they can use that knowledge to “reach across” to experienced C++ developers who don’t have as deep of Rust knowledge and improve the chances of a good answer. – Shepmaster May 08 '19 at 20:54
  • @Shepmaster Yes, and I was a bit surprised by your comment immediately prior to mine, but now I understand the last sentence of your first comment better. – Kyle Strand May 08 '19 at 20:58
  • I feel like there's a lot of baggage to the statement "In Rust, the main tool for abstraction are traits". I assume you're using *abstraction* in a very narrow sense (obviously, functions, `for` loops, `struct`s and lifetimes are also abstractions) but I'm not sure what specific kind of abstraction you're talking about, possibly because I also don't really understand C++ concepts so I don't understand in what context one would want to compare them. – trent May 08 '19 at 21:02
  • What exactly do you mean when you say "zero-overhead" and "zero cost"? That is, which factors do you intend to be considered? Which should not be considered? For example, a C++ template can have a ton of space overhead, as each instantiation takes up space in the executable. An abstract class, on the other hand, permits code sharing, potentially reducing executable size. Does that mean space is not a concern for you? (Or, if "zero" is not that important, maybe drop those phrases from the question?) – JaMiT May 15 '19 at 16:27
  • @JaMiT I agree, mentioning those isn't really helpful. I edited the question to remove those. – Lukas Kalbertodt May 15 '19 at 16:35

1 Answers1

47

Disclaimer: I have not yet used concepts, all I know about them was gleaned from the various proposals and cppreference, so take this answer with a grain of salt.

Run-Time Polymorphism

Rust Traits are used both for Compile-Time Polymorphism and, sometimes, Run-Time Polymorphism; Concepts are only about Compile-Time Polymorphism.

Structural vs Nominal.

The greatest difference between Concepts and Traits is that Concepts use structural typing whereas Traits use nominal typing:

  • In C++ a type never explicitly satisfies a Concept; it may "accidentally" satisfy it if it happens to satisfy all requirements.
  • In Rust a specific syntactic construct impl Trait for Type is used to explicitly indicates that a type implements a Trait.

There are a number of consequences; in general Nominal Typing is better from a maintainability point of view -- adding a requirement to a Trait -- whereas Structural Typing is better a bridging 3rd party libraries -- a type from library A can satisfy a Concept from library B without them being aware of each other.

Constraints

Traits are mandatory:

  • No method can be called on a variable of a generic type without this type being required to implement a trait providing the method.

Concepts are entirely optional:

  • A method can be called on a variable of a generic type without this type being required to satisfy any Concept, or being constrained in any way.
  • A method can be called on a variable of a generic type satisfying a Concept (or several) without that method being specified by any Concept or Constraint.
  • Constraints (see note) can be entirely ad-hoc, and specify requirements without using a named Concept; and once again, they are entirely optional.

Note: a Constraint is introduced by a requires clause and specifies either ad-hoc requirements or requirements based on Concepts.

Requirements

The set of expressible requirements is different:

  • Concepts/Constraints work by substitution, so allow the whole breadth of the languages; requirements include: nested types/constants/variables, methods, fields, ability to be used as an argument of another function/method, ability to used as a generic argument of another type, and combinations thereof.
  • Traits, by contrast, only allow a small set of requirements: associated types/constants, and methods.

Overload Selection

Rust has no concept of ad-hoc overloading, overloading only occurs by Traits and specialization is not possible yet.

C++ Constraints can be used to "order" overloads from least specific to most specific, so the compiler can automatically select the most specific overload for which requirements are satisfied.

Note: prior to this, either SFINAE or tag-dispatching would be used in C++ to achieve the selection; calisthenics were required to work with open-ended overload sets.

Disjunction

How to use this feature is not quite clear to me yet.

The requirement mechanisms in Rust are purely additive (conjunctions, aka &&), in contrast, in C++ requires clauses can contain disjunctions (aka ||).

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • I have made this answer as exhaustive as possible; if there are any issues, please let me know. – Matthieu M. May 09 '19 at 08:34
  • 1
    "Structural Typing is better a bridging 3rd party libraries" - Only if the requirements are formulated in a way that the type can be adapted. If there's a requirement to be able to call a `read` member on a type, but the library only provides `Read`, there's very little you can do. The Rust `impl` blocks can adapt. – Sebastian Redl May 09 '19 at 08:39
  • I've been wondering something else about C++ concepts, but I don't know if the question even makes sense or should be answered here. So Rust's trait solver is often described as "basically Prolog". I wonder if some similar kind of logic needs to be done inside C++ compiler now or if concepts don't require that for some reason. Sorry again if this question is vague and maybe not useful. – Lukas Kalbertodt May 09 '19 at 08:52
  • 1
    @LukasKalbertodt: There is no Hindley Milner type inference ongoing with C++ concepts; so no. – Matthieu M. May 09 '19 at 11:17
  • 1
    @SebastianRedl: Sure. However that's the difference between always needing an adapter struct (Rust) vs only needing an adapter struct to smooth away differences (C++). Furthermore, using free functions rather than member functions allows only needing to add one function (`read` here, which delegates to `Read`). – Matthieu M. May 09 '19 at 11:20
  • 1
    Is there anything in C++ that can be compared to other kinds of associated items, such as associated constants or associated types? – Ralf Jung May 15 '19 at 19:23
  • 2
    @RalfJung: In C++ this would static member variables (const ones) and nested typedefs or nested classes. – Matthieu M. May 16 '19 at 06:32
  • There's also a meaningful difference in quantification: in C++, you cannot universally quantify constraints (e.g., constructible from any copyable type). – Jeff Garrett Dec 10 '21 at 01:23
  • @JeffGarrett Could you be a bit more specific please, difference between what and what? If you mean Rust and C++ how can you universally qualify constraints in Rust? Also there are lot of concepts in C++ that refer to constructors that use language features, especially the universal bracket initialization syntax which works both on C and C++ types. – The Floating Brain May 29 '22 at 19:45
  • Try to define a concept `Any` that captures "constructible from any copyable type". `std::any` should satisfy this concept. By "you cannot universally quantify", I meant that you cannot write this concept in C++. In Rust, you write `trait Any { fn new(t: T) -> Self; }`. The methods can be generic in Rust, and not in C++. This is what I'm trying to say. – Jeff Garrett May 29 '22 at 22:03