Rust has found that allowing interfaces to define default values for its associated entities is valuable:
Carbon would benefit in the same ways.
Rust supports specifying defaults for methods, interface parameters, and associated constants.
This proposal defines both how defaults for interface members are specified in Carbon code as well as final interface members in the generics details design doc.
This proposal advances these goals of Carbon:
final makes the code more predictable to
users of that member.Rust has observed (1, 2) that interface defaults could be generalized into a feature for reusing definitions between impls. This would involve allowing more specific implementations to be incomplete and reuse more general implementations for anything unspecified.
However, they also observed:
[To be sound,] if an impl A wants to reuse some items from impl B, then impl A must apply to a subset of impl B's types. ... This implies we will have to separate the concept of "when you can reuse" (which requires subset of types) from "when you can override" (which can be more general).
This is a source of complexity that we don't want in Carbon. If we do eventually support inheritance of implementation between impls in Carbon, it will do this by explicitly identifying the impl being reused instead of having it be determined by their specialization relationship.
Here are the reasons we considered for not allowing interfaces to provide default implementations of interfaces they require:
TotalOrder also must explicitly implement PartialOrder, possibly with an
empty definition. The problem arises since querying whether PartialOrder
is implemented for a type does not require that an implementation of
TotalOrder be visible.PartialOrder is implemented explicitly.The rules for blanket impls already provide resolution of the questions about coherence and priority and make it clear that the provided definition of the required interface will be external.
finalThere are a few reasons to support final on associated entities in the
interface:
DynPtr.The main counter-argument is that you could achieve something similar using a
final impl:
interface I {
fn F();
final fn CallF() { F(); }
}
could be replaced by:
interface IImpl {
fn F();
}
interface I {
extends IImpl;
fn CallF();
}
final impl (T:! IImpl) as I {
fn CallF() { F(); }
}
This is both verbose and a bit awkward to use since you would need to
impl as IImpl but use I in constraints.