Interfaces
Interfaces are a big part of Getty, so let's take some time now to learn a bit about them.
Interface
In Getty, an interface is a function whose parameter list specifies
constraints and behaviors. For example, the following code defines an interface
that requires from its implementations three associated types (Context, O,
E) and one method (serializeBool).
fn BoolSerializer(
// Context, O, and E are associated types that must be provided.
comptime Context: type,
comptime O: type,
comptime E: type,
// methods lists every method that implementations of BoolSerializer must
// provide or can override.
//
// If a method is not provided by an implementation, it is up to the
// interface to decide what happens. Generally, a compile error is raised,
// an error is returned, or a default implementation is used.
comptime methods: struct {
serializeBool: ?fn (Context, bool) E!O = null,
},
) type
The return value of an interface is a namespace (i.e., a struct type with no
fields) that contains two declarations: an interface type and an
interface function.
struct {
// Iface is an interface type. These generally have:
//
// * A field to store an instance of an implementation.
// * Wrapper declarations for important associated types.
// * Wrapper methods that define the interface's behavior.
pub const Iface = struct {
context: Context,
pub const Ok = O;
pub const Error = E;
pub fn serializeBool(self: @This(), value: bool) Error!Ok {
if (methods.serializeBool) |f| {
return try f(self.context, value);
}
@compileError("serializeBool is unimplemented");
}
};
// boolSerializer is an interface function.
//
// Its job is to return a value of the interface type, also known as
// an interface value.
pub fn boolSerializer(self: Context) Iface {
return .{ .context = self };
}
};
Naming Conventions
-
Interface types are always named after the interface's import path. For example, the interface type for the
getty.de.SeqAccessinterface is named@"getty.de.SeqAccess". -
Interface functions are always named after the interface in
camelCaseformat. For example, the interface function for thegetty.de.SeqAccessinterface is namedseqAccess.
Implementation
To implement a Getty interface, call the interface and apply pub
usingnamespace to the returned value. An interface type and function will be
imported into your implementation.
const std = @import("std");
const UselessSerializer = struct {
pub usingnamespace BoolSerializer(
@This(),
void,
error{},
.{},
);
};
const OppositeSerializer = struct {
pub usingnamespace BoolSerializer(
Context,
Ok,
Error,
.{ .serializeBool = serializeBool },
);
const Context = @This();
const Ok = void;
const Error = error{};
fn serializeBool(_: Context, value: bool) Error!Ok {
std.debug.print("{}\n", .{!value});
}
};
Usage
To use a value of OppositeSerializer as an implementation of BoolSerializer:
pub fn main() !void {
// Create a value of the implementing type.
const s = OppositeSerializer{};
// Create an interface value from `s` using the interface function.
const bs = s.boolSerializer();
// Use the interface value for all of our interface-y needs!
try bs.serializeBool(true);
try bs.serializeBool(false);
}