C++20 concepts are not like Rust traits
Wed 21 August 2019At writing, Rust's Wikipedia currently says the following:
Functions can be given generic parameters, which usually require the generic type to implement a certain trait or traits. Within such a function, the generic value can only be used through those traits. This means that a generic function can be type-checked as soon as it is defined. This is in contrast to C++ templates, which are fundamentally duck typed and cannot be checked until instantiated with concrete types. C++ concepts address the same issue and are expected to be part of C++20 (2020).
But C++ concepts (as currently proposed) are very different from Rust traits, and do not allow for the parameterised function to be type-checked only once.
Rust traits are implemented explicitly, whereas C++ concept constraints are
implicitly met. This means that concept-constrained templates can legally
invoke behaviour not defined by the concept. So, the following code compiles in
g++ v9.2 with flags --std=c++2a -fconcepts
:1
1 #include <string> 2 3 template<typename T> 4 concept bool Stringable = requires(T a) { 5 {a.stringify()} -> std::string; 6 }; 7 8 class Cat { 9 public: 10 std::string stringify() { 11 return "meow"; 12 } 13 14 void pet() { 15 } 16 }; 17 18 template<Stringable T> 19 void f(T a) { 20 a.pet(); 21 } 22 23 int main() { 24 f(Cat()); 25 return 0; 26 }
The Rust equivalent would not compile:
1 trait Stringable { 2 fn stringify() -> String; 3 } 4 5 struct Cat { 6 } 7 8 impl Cat { 9 fn pet() {} 10 } 11 12 impl Stringable for Cat { 13 fn stringify() -> String { 14 "meow".to_string() 15 } 16 } 17 18 fn f<T: Stringable>(a: T) { 19 a.pet(); // error[E0599]: no method named `pet` found for type `T` in the current scope 20 } 21 22 fn main() { 23 let cat = Cat{}; 24 f(cat); 25 }
C++ concept-constrained templates are still only type checked when concrete instantiation is attempted – they just give better, sooner error messages for types that don't comply with the constraint, rather than the long stream of nonsense that failed template instantiations output in C++ without concepts.
I think it's quite common for people to think that concepts are the same as traits. They look similar syntactically, and also the realities of them aren't well known because they aren't yet in a standard. I hope this can clarify things for anyone curious, and help anyone adjust expectations before the C++20 standard is released.
Rust traits comparisons with other language constructs
Although Rust traits are very different from C++20 concepts, they have similarities to a lot of other language constructs for polymorphism:
-
Haskell typeclasses: Rust traits are based on these, but Rust does not have higher kinded types2, and Rust enforces global uniqueness on trait implementations34. This means that there is at most one implementation of a trait for any given type. This is not enforced in Haskell, but it is discouraged to take advantage of this.5
-
Java interfaces: when Rust traits are used dynamically, they are analogous to Java interfaces, except without the
extend
functionality available in Java.6