Principle: One static open extension mechanism
Background
In C++, a single function may be overloaded with definitions in multiple files.
The ADL name
lookup rule even allows an unqualified call to resolve to functions defined in
different namespaces. These rules are used to define extension points with
static dispatch for operator overloading and functions like
swap
.
Nothing in C++ restricts the signatures of function overloads. This means that if overloading is used as an extension point to define an operation for a variety of types, there is no way to type check generic code that tries to invoke that operation over those types.
Further, in C++, all non-member functions can be found in this way, if they are declared in the same namespace as a type that could be associated with an argument list in a call. There is no straightforward opt-out mechanism in a function declaration, and while there are opt-out mechanisms at call sites, they are rarely used. As a consequence, many non-member functions with the same name can form part of an overload set, even if they provide unrelated functionality, and there is no indication in the code of which functions in different namespaces are intended to expose the same capability.
Principle
In Carbon, interface
s are the only static
open extension mechanism.
Each type may define its own implementation of each interface. Generic code can be written that works with any type implementing the interface. That code can be type checked independent of which type the generic code is instantiated with by using the fact that the interface specifies the signatures of the calls.
To keep the language simple, this is the only static open extension mechanism in
Carbon. This means that function overloading is limited in Carbon to only
signatures defined together in the same library. It also means that to
interoperate with C++, the operators and swap
need to have corresponding
interfaces on the Carbon side.
The main advantage of interfaces as an open extension mechanism over open overloading is allowing generics to be type checked separately. In addition, they are less context sensitive. Generics are coherent, while open function overloading can resolve names differently depending on what is imported. Closed overloading in Carbon also simplifies what gets exported to C++ from Carbon. Interface implementations express intent by being explicit, in contrast to how adding a function to a cross-file overload set can be accidental.
Interfaces provide an way to group functions together, and express the constraint that all of the functions in the group are implemented. Consider a random-access iterator, which has a number of methods. If a C++ template function only accesses some of those methods which happens to match the subset defined for a type, the code will work temporarily but fail later when the code is changed to use a different subset.
This helps achieve the Carbon Goal of code that is easy to read, understand, and write.
Alternatives considered
Another approach to operator overloading is to use methods with a specific name.
In C++ these start with the
operator
keyword.
Python uses method with names starting and ending with double underscores.
Interfaces are more flexible about where implementations may be defined. For
example, with special method names, +
on a Vector(T)
class could only be
defined as part of the Vector(T)
definition. With interfaces, additionally +
for Vector(MyType)
could be implemented with MyType
. C++ provides this
flexibility by also permitting non-method operator overloads, but this brings
with it the cost of selecting a best-matching operator from a potentially very
large open overload set.