Interfaces in the Chapel Standard Library¶
Interfaces in Chapel are similar to a feature of the same name in object-oriented
languages such as Java, and to traits in Rust.
They provide a way to mark a type as exhibiting certain behavior or implementing
certain functionality. For instance, a hashable
interface marks a type as
specifying a hash
method (which would allow it to be used in
associative domains or sets). The Chapel standard
library provides several interfaces that it expects code authors to implement
to opt in to various functionality.
The syntax for implementing an interface is similar to that for extending a
class; however, both records and classes can implement an interface. The
following example demonstrates a record R
that implements the hashable
interface mentioned in the previous paragraph.
record R : hashable {
proc hash(): uint {
return 0;
}
}
If a type implements multiple interfaces, they can be listed in a comma-separated list:
record R : hashable, contextManager {
proc hash(): uint { /* ... */ }
proc enterThis() { /* ... */ }
proc exitThis(in err: owned Error?) { /* ... */ }
}
The same syntax also applies to classes. If a class extends another class in addition to implementing an interface, the parent class and implemented interfaces can be combined into a single list. It’s recommended that the parent class be listed first.
class Child : Parent, hashable, contextManager {
proc hash(): uint { /* ... */ }
proc enterThis() { /* ... */ }
proc exitThis(in err: owned Error?) { /* ... */ }
}
Semantics¶
Generally, an interface can require any number and combination of the following three things from a type:
A method or procedure with a particular type signature. For instance, the
hashable
interface requires a method calledhash
that accepts no arguments and returns a value of typeuint
.For a type’s function or method to match a function or method required by an interface, it must have the same name, formals, and return type.
An associated type. For instance, the
contextManager
interface requires an associated typecontextReturnType
which represents the type of resource handled by the context manager. ForcontextManager
specifically, this type can usually be inferred. However, in general, associated types must be explicitly specified by the user.See Providing Associated Types for an example of providing a
contextReturnType
explicitly.Another interface. For instance, the
serializable
interface represents a combination of thewriteSerializable
,readDeserializable
, andinitDeserializable
interfaces. For a type to implementserializable
, it must satisfy the other three interfaces as well.
A function or an associated type that serves to satisfy a requirement of an interface is typically called a witness.
When a type is marked as implementing an interface, the Chapel compiler will
ensure that it satisfies all of the interface’s requirements. The compiler will
do so by checking the current scope and resolving any functions that fit the
interface criteria. Note that because only the current scope is searched,
methods defined in other files (and not made available by a use
or import
statement) cannot be used as witnesses for an interface.
Providing Associated Types¶
An associated type is defined using a paren-less type-returning method. The
following code snippet demonstrates specifying myAssociatedType
of record
R
to be int
.
record R {
proc myAssociatedType type do return int;
}
var r: R;
writeln(r.myAssociatedType : string); // prints 'int(64)'
Currently, contextManager
is the only interface that requires an associated
type. This type, named contextReturnType
, is used to determine the expected
return type of the enterContext
method. Because of this, it’s usually
sufficient to declare this associated type in the following manner:
record R : contextManager {
proc contextReturnType type do return this.enterContext().type;
// contextManager methods
proc enterContext() { return 0; }
proc exitContext(in err: owned Error?) {}
}
Since the above definition of contextReturnType
works for most context
managers, the compiler will attempt to automatically provide the contextReturnType
.
Thus, the following definition is equivalent to the one above:
record R : contextManager {
// contextManager methods
proc enterContext() { return 0; }
proc exitContext(in err: owned Error?) {}
}
Built-In Interfaces¶
The Chapel standard library defines several interfaces. Implementing these interfaces is required to opt in to various language features. These interfaces, as well as the features they are used to implement, are as follows:
The
hashable
interface is used to expose a type’s hash function, making it usable for constructs in the language backed by a hash table or a similar data structure; such constructs include associative domains or sets from theSet
module.For more information on the
hash
method and thehashable
interface, please see Hashing a Record.The
contextManager
interface is used with types that support themanage
statement.For more information on the
manage
statement and thecontextManager
interface, please see the tech note for the ‘manage’ statement, as well as the relevant section of the spec.The
serializable
interface is used with types that can be written out and read back in. For more details on what this means, see the IO serializers tech note. Theserializable
interface logically consists of the following three interfaces:The
writeSerializable
interface is used with types that can be written out using aserialize
method.The
readDeserializable
interface is used with types that can be populated from a serializer once they have been created.The
initDeserializable
interface is used with types that can be constructed from a deserializer. This differs from the ‘read-serialization’ because such types need not be allocated in advance of being deserialized.
Auto-Implemented Interfaces¶
The Chapel compiler attempts to automatically generate certain methods for types,
to preclude the need for users to implement their own. For instance, records
without user-defined ==
and !=
operators that lack a hash
method
are automatically supplied with a compiler-generated hash
. This serves to
reduce the boilerplate for types that don’t require custom logic. For some
interfaces (described in this section), the compiler will also automatically
generate an implementation if the required methods were generated.
For instance, consider the following snippet:
record R {
var x: int;
}
var D: domain(R);
writeln(D);
writeln(new R());
In the snippet, a newly-defined record R
could be used in an associative
domain and printed out using writeln
. Using these features requires the
record to be hashable and serializable, respectively. However, the user was
not required to manually implement hashable
and writeSerializable
.
Generally (except for transitory measures; see Migration Support), the compiler will not generate methods if a user-defined method with the same name exists; it will therefore also not generate the corresponding interface. Additionally, certain other situations will stop the compiler from generating methods and interface implementations; consult the documentation for specific features to learn more.
The following interfaces are automatically implemented by the compiler:
hashable
writeSerializable
readDeserializable
initDeserializable
Migration Support¶
Prior to the Chapel 1.32 release, features that currently require interfaces
were handled by duck-typing. For example, the presence of a hash
method
was sufficient to make a type hashable, and there was no need to also implement
an interface. To aid the gradual migration from duck-typed to interface-based
code, the compiler will automatically generate interface instances from
user-defined methods, without requiring an explicit record R : hashable
declaration. This functionality is transitory, and should issue warnings.
Future Work¶
Planned work for interfaces includes stabilizing the following features:
a syntax for declaring an interface
a way to implement an interface for a type declared elsewhere
functions whose arguments are constrained to implement an interface