Interface Basics
The essence of an interface is a fat pointer.
A simplest interface
The following is an interface IBasic that defines an interface method getMsg():
pub const IBasic = struct {
pub const Vtable = zoop.DefVtable(@This(), struct {
getMsg: *const fn (*anyopaque) []const u8,
});
pub usingnamespace zoop.Api(@This());
ptr: *anyopaque,
vptr: *Vtable,
pub fn Api(comptime I: type) type {
return struct {
pub fn getMsg(self: I) []const u8 {
return self.vptr.getMsg(self.ptr);
}
};
}
}Vtable,ptr,vptr,Apiare all newly introduced keywords- Lines 2-4 define the
Vtablekeyword of the interface through zoop.DefVtable , and theVtabletype contains fields for all interface methods - Line 5, introduce the parent interface and all ApiMethod defined in Api through zoop.Api
- Lines 7-8, fat pointer data
- Lines 10-15 define the
getMsg()ApiMethod through Api
Note
All interfaces automatically inherit the zoop.IObject interface
Default implementation of the interface
When a method of an interface has the same implementation most of the time, you can specify a default implementation directly when defining the interface. For example, if IBasic.getMsg() returns "Hello" most of the time, we can define IBasic like this:
pub const IBasic = struct {
pub const Vtable = zoop.DefVtable(@This(), struct {
getMsg: *const fn (*anyopaque) []const u8 = &_getMsg,
});
pub usingnamespace zoop.Api(@This());
ptr: *anyopaque,
vptr: *Vtable,
fn _getMsg(_: *anyopaque) []const u8 {
return "Hello";
}
pub fn Api(comptime I: type) type {
return struct {
pub fn getMsg(self: I) []const u8 {
return self.vptr.getMsg(self.ptr);
}
};
}
}In this way, all classes that declare to implement the IBasic interface do not need to implement their own version of the getMsg() method if they accept the default implementation of getMsg() in the interface definition.
Interface inheritance
Interface inheritance uses the same keyword extends as class inheritance. Below we define a new interface ISubClass that inherits IBasic and add a new ApiMethod named setMsg() to the new interface:
pun const ISubClass = struct {
pub const extends = .{IBasic};
pub const Vtable = zoop.DefVtable(@This(), struct {
setMsg: *const fn (*anyopaque, msg: []const u8) void,
});
pub usingnamespace zoop.Api(@This());
ptr: *anyopaque,
vptr: *Vtable,
pub fn Api(comptime I: type) type {
return struct {
pub fn setMsg(self: I, msg: []const u8) void {
self.vptr.setMsg(self.ptr, msg);
}
};
}
}To make it easier to understand, we list the code expanded after the above code is calculated by comptime:
pun const ISubClass = struct {
pub const extends = .{IBasic};
pub const Vtable = struct {
...// Omit fields from zoop.IObject
getMsg: *const fn (*anyopaque) []const u8 = &IBasic._getMsg,
setMsg: *const fn (*anyopaque, msg: []const u8) void,
});
ptr: *anyopaque,
vptr: *Vtable,
...// Omit functions from zoop.IObject
pub fn getMsg(self: ISubClass) []const u8 {
return self.vptr.getMsg(self.ptr);
}
pub fn setMsg(self: ISubClass, msg: []const u8) void {
self.vptr.setMsg(self.ptr, msg);
}
}- The extra fields in
ISubClass.Vtableare calculated from zoop.DefVtable ISubClass.getMsgmethod, from zoop.Api calculation
Note
There cannot be methods with the same name in all interface methods of an interface and all its parent interfaces, otherwise a compilation error will occur.
Interface Implementation
The class implements the interface using the same keyword extends as inheritance. Below we define a class Basic that implements the IBasic interface:
pub const Basic = struct {
pub const extends = .{IBasic};
pub usingnamespace zoop.Fn(@This());
msg: []const u8,
mixin: zoop.Mixin(@This()),
pub fn init(self: *Basic, msg: []const u8) void {
self.msg = msg;
}
pub fn Fn(comptime T: type) type {
return zoop.Method(.{
struct {
pub fn getMsg(this: *T) []const u8 {
return this.cast(Basic).msg;
}
},
});
}
}- Line 2, implement the
IBasicinterface through theextendskeyword declaration - Lines 12-20 define the
getMsg()method required to implementIBasic
Note
- The interface method must be inheritable, that is, the method must be defined in Fn
- If an interface method is not implemented and no default implementation is defined in the interface, a compilation error will occur
- If any inheritable method of a class (including those inherited from parent classes) has the same name as a method in an interface but with different parameters/return values, there will be a compilation error (except for the first parameter, which is the
thispointer) - If the parent class has an inheritable method that matches a method definition in the interface, this class does not need to implement the method, and the interface will use the parent class version of the implementation
- The subclass automatically inherits and implements all interfaces implemented by the parent class
- All classes automatically implement the zoop.IObject interface
Inheriting and implementing interfaces
Inheriting a class and implementing an interface can be done at the same time. Below is a class called SubClass that inherits Basic and implements the ISubClass interface:
pub const SubClass = struct {
pub const extends = .{Basic, ISubClass};
pub usingnamespace zoop.Fn(@This());
mixin: zoop.Mixin(@This()),
pub fn init(self: *SubClass, msg: []const u8) void {
self.cast(Basic).init(msg);
}
pub fn Fn(comptime T: type) type {
return zoop.Method(.{
struct {
pub fn setMsg(this: *T, msg: []const u8) void {
this.cast(Basic).msg = msg;
}
},
});
}
}- In line 2, the
extendskeyword is used to inheritBasicand declare the implementation ofISubClass - Line 8, call the parent class
Basic.init()to initialize - Line 14, implement the
ISubClass.setMsg()interface method
Note
SubClass is dependent on the inherited Basic.getMsg() and implements the ISubClass.getMsg() interface method
