While discussing the pro and contra of single and multiple inheritance, traits are often mentioned as the golden mean. Afterwards the discussion gets confusing, because nobody really knows what a trait actually is. The Wikipedia page isn't helpful in this case. The definitions are often vague or even contradictory. Their meaning changes between programming languages.

In this article I'll describe traits formally and in their implementations in the programming languages Scala, C++ and D. The concept of mixins is often confused with traits, so I will refrain from mentioning them here and explain them in a different article.

Formal Traits

The basic paper is Traits: Composable Units of Behaviour, where traits are described by six properties:

  1. A trait provides a set of methods that implement behaviour.
  2. A trait requires a set of methods that serve as parameters for the provided behaviour.
  3. Traits do not specify any state variables, and the methods provided by traits never access state variables directly.
  4. Classes and traits can be composed from other traits, but the composition order is irrelevant. Conflicting methods must be explicitly resolved.
  5. Trait composition does not affect the semantics of a class: the meaning of the class is the same as it would be if all of the methods obtained from the trait(s) were defined directly in the class.
  6. Similarly, trait composition does not affect the semantics of a trait: a composite trait is equivalent to a flattened trait containing the same methods.

The first three properties describe something like an abstract class where all attributes are static, but the methods may call non-static methods of the super class. The resolving in (4.) is usually done by declaring a new method, which overrides the conflicting methods. If there are no conflicts by definition, composition order becomes irrelevant, since it would only be used to resolve conflicts.

Traits in Scala

The Scala programming language tries to implement traits faithfully, but has to respect its platform, which is the JavaVM. When the Scala compiler processes a trait A the resulting class file contains an interface A. The methods declared within a trait are moved into every class that inherits from the trait. On top of this there are some type checks to keep everything safe. As an example consider the typical class diamond.

class Top {
  def f = { ... }
}
class Left extends Top {
  override def f = { ... }
}
class Right extends Top {
  override def f = { ... }
}
class Bottom extends Left with Right { }

The Scala compiler will complain that Right must be a trait, because of with Right. Classes can only inherit from at most one other class, but many traits. So let's change Right to be a trait.

class Top {
  def f = { ... }
}
class Left extends Top {
  override def f = { ... }
}
trait Right extends Top {
  override def f = { ... }
}
class Bottom extends Left with Right { }

This code compiles and if you evaluate new Bottom().f the method from the Right trait will be called. Remember that methods of traits are moved into the inheriting class, i.e. Bottom. This can lead to seemingly contradictory error messages, if we remove the f method from Top.

class Top {
}
class Left extends Top {
  def f = { ... }
}
trait Right extends Top {
  override def f = { ... }
}
class Bottom extends Left with Right { }

The compiler will complain that Right::f mustn't have the override qualifier, since there is no f in Top, which could be overridden and Right extends Top. The compiler will tell you that Right::f must have the override qualifier, since the method ends up in Bottom and overrides Left::f.

This shows that traits can get confusing, when you go down to the details like method qualifiers. We won't dig deeper into stuff like self types, which refers to the "required set of methods" of the second trait property above.

Traits in C++

In C++ traits are more like a design pattern), since C++ allows unrestricted multiple inheritance. The only trait I could find is the iterator_trait. It contains multiple attributes which hold types that are associated with iterators. For example the attribute iterator_trait<I>::difference_type contains the compile-time information of a signed integral type that can be used to represent the distance between two iterators. The iterator_trait is implemented by using a C++ feature called "partial specialization", which is higher template magic.

Traits in D

The D programming language will also feature traits in version 2.0, but the description sounds a little like the C++ version.

Traits are extensions to the language to enable programs, at compile time, to get at information internal to the compiler. This is also known as compile time reflection. It is done as a special, easily extended syntax (similar to Pragmas) so that new capabilities can be added as required.

For example __traits(isAbstractClass, C) is an expression that returns a boolean, representing whether C is an abstract class or not. The motivation is:

(1) Giving better error messages inside generic code than the sometimes hard to follow compiler ones. (2) Doing a finer grained specialization than template partial specialization allows for.

So traits in D are somewhere between pragmas and C++ traits. They are hardcoded and can't be extended by the programmer.

Conclusion

There seem to be two distinct ideas about traits. In an academic context the first concept of "set of methods" is probably the dominant use. For C++ and friends traits are about "compile time information". I hesitate to call the latter real traits, since there only seem to be special cases. In D they are a hardcoded set of functions and in C++ there is probably no other trait than iterator_trait.

The general meaning of traits should be something that fulfills the six properties described above to avoid confusion. This concept looks like a powerful, yet safe mechanism between single and multiple inheritance.

Thanks to Sebastian Buchwald, Sebastian Gregorzyk and Christian Jülg for proofreading
© 2009-05-18
qznc