Interfaces
Harneet now ships with full interface support in both AST and bytecode/JIT modes. Interfaces enable structural typing, zero-boilerplate polymorphism, and powerful runtime type inspection. This guide walks through every major capability.
Overview
Interfaces describe behavior rather than data. Any struct that exposes the required methods automatically satisfies an interface—no explicit implements clause is needed.
Key characteristics:
- Implicit implementation: a struct satisfies an interface if it provides all required methods with matching signatures.
- Structural typing: method sets determine compatibility (no inheritance hierarchy).
- First-class values: interface instances carry both the interface type and the concrete value.
- Works everywhere: assignment, function parameters, collections, and serialization all support interface values.
Declaring Interfaces
Interfaces can reference other interfaces (embedding) or define their own method set. Methods look exactly like struct methods.
Implementing Interfaces
Implementation is implicit—no keywords required. If a struct declares every method in the interface, it automatically satisfies it.
Interface Assignment
Assign a struct to an interface variable to wrap it automatically:
Behind the scenes Harneet stores:
- The interface type information.
- The concrete struct value.
This metadata is used for dispatch and runtime checks.
Dynamic Dispatch
Method calls on interface values automatically invoke the concrete implementation:
Dispatch works identically in AST and bytecode modes. Bytecode uses a lightweight map wrapper internally and unwraps values on demand, so no special syntax is needed.
Type Assertions
Use value.(Type) to recover concrete values or validate types at runtime.
Type assertion failures produce descriptive runtime errors that include the actual concrete type.
Suggested Pattern
Wrap assertions with helper functions to emit friendly errors:
Type Switches (.type() Syntax)
Harneet uses a method-like syntax for type switches:
Why .type() instead of Go’s .(type)? It matches Harneet’s method call ergonomics, avoids parser ambiguities, and is easier to remember.
Type switches automatically unwrap interface values when a case matches, binding the concrete value to the switch variable (here v).
Bytecode/JIT Guarantees
All interface features work in bytecode mode (the default execution model):
- Interface registration and metadata
- Satisfaction checks on assignment
- Dynamic dispatch via
interface.method() - Type assertions
- Type switches
The implementation uses the same opcodes described in BYTECODE_INTERFACE_IMPLEMENTATION_PLAN.md, so behavior is consistent across execution modes.
Comprehensive Example
Testing & Examples
Extensive interface-focused samples live in examples/interfaces/:
bytecode_smoke_test.hainterface_edge_cases_test.hainterface_assignment_test.hatype_assertion_test.hatype_switch_comprehensive.ha
Run them together via:
How to Fix Interface Errors
The typechecker performs structural checks to ensure that concrete types actually implement the interfaces you use in:
- Variable declarations:
var g Greeter = person - Assignments:
g = person - Function and method calls:
useGreeter(person)whenuseGreeter(g Greeter)
Here are the most common error patterns and how to fix them.
1. Missing All Interface Methods
Example error
type
Persondoes not implement interfaceGreeterwhen assigning tog: missing all interface methods
Why it happens
- The struct type (
Person) has no methods registered that match the interfaceGreeter. - The typechecker cannot find any implementation of the interface’s method set on that type.
How to fix it
- Add all required methods from
GreetertoPerson: - Same method names.
- Compatible parameter and return types.
- Or, change the variable or parameter type to a different interface or concrete type that your struct already implements.
2. Missing a Specific Method
Example error
type
MyStructdoes not implement interfaceMyInterfacewhen assigning toi: missing methodDoSomething
Why it happens
MyStructdefines some methods, but one or more required methods are missing entirely.- In this example,
MyInterfacedeclaresDoSomething, butMyStructdoes not.
How to fix it
- Add the missing method(s) to
MyStruct: - Use exactly the same method name as in the interface.
- Ensure the parameter and return types match the interface declaration.
- Or, adjust the type of the variable/parameter (
i) or change the interface definition if its contract is no longer what you want.
3. Method Signature Mismatch
Example error
method
DoSomethingon typeMyStructdoes not match interfaceMyInterfacewhen assigning toi
Why it happens
MyStructdefines a method with the correct name, but its signature does not match the interface.- Common mismatches:
- Different number of parameters.
- Different parameter types (e.g.,
stringvsint). - Different return types or counts.
How to fix it
- Update the method on the struct to match the interface exactly:
- Same parameter count and types.
- Same return type list.
- Or, adjust the interface if the intended contract has changed.
4. Function Arguments That Don’t Implement the Interface
When passing values into functions that expect an interface parameter, the typechecker applies the same structural rules.
Example
Typical error
type
Baddoes not implement interfaceGreeterwhen assigning toargument 1: missing methodGreet
How to fix it
- Implement
Greet()onBad, or - Change the parameter type of
useGreeterto a different interface or a concrete type that doesn’t requireGreet, or - Pass a different type that already implements
Greeter.
5. Empty Interfaces
Empty interfaces (no methods) are always satisfied by any type:
You will not see structural interface errors for empty interfaces, because there is no method contract to enforce.
6. When Static Checking Is Skipped
In a few cases Harneet’s typechecker intentionally defers to runtime:
- The interface type is not known in the current compilation unit (for example, imported from another module that the checker does not see as a
type ... interfacedeclaration). - The static type of the value is itself an interface.
In these scenarios, assignments and calls are allowed, and any incompatibilities are surfaced by runtime checks instead of static errors.
Interfaces unlock ergonomic abstractions without sacrificing performance or clarity. Pair them with Harneet’s structural typing, rich standard library, and async features to build expressive systems with minimal boilerplate.