Interfaces
An interface defines a behavioral contract that a concrete Sec type may explicitly implement.
Interfaces describe what a value or type can do without prescribing how the implementing
type stores its data.
interface Reader {
fn Read(buffer: ref mut byte[]) Result[int, IOError]
}
A concrete type declares conformance on its primary implementation:
type FileReader struct {
handle: FileHandle,
}
impl FileReader implements Reader {
fn Read(buffer: ref mut byte[]) Result[int, IOError] {
...
}
}
Core rule: Interface conformance is explicit.
Sec does not use accidental structural conformance merely because a type happens
to contain members with matching names.
Behavioral Contracts
An interface describes observable API requirements.
It may require:
- instance methods;
- mutable instance methods;
- ordinary parameters with copy, borrow or consuming modes;
- static methods where meaningful;
- properties;
- associated or nested declarations where their own declaration rules permit them.
An interface does not extend the stored representation of an implementing type.
The implementing type decides how the required behavior is realized.
Interface Declaration
The basic form is:
interface Name {
requirements
}
Example:
interface Identified {
fn Identifier() Identifier
}
Interface method declarations have signatures but no implementation bodies.
An interface is a named contract type. Different interfaces remain distinct even
when they happen to declare identical requirements.
Empty Interfaces
An interface may contain no members:
interface Marker {
}
A concrete type may explicitly implement it:
type MarkedValue struct {
value: int,
}
impl MarkedValue implements Marker {
}
An empty interface can therefore serve as explicit semantic classification.
Empty does not mean that every type implements it automatically.
Conformance remains explicit.
Interface Members
A typical interface may combine several contract forms:
interface Device {
fn Name() string
mut fn Reset() void
property Enabled: bool {
get
set enabled
}
static fn Parse(value: string) Result[Self, ParseError]
}
Each requirement contributes to the complete conformance contract.
Method overload rules, parameter ownership, result types, generics and property
semantics retain their ordinary Sec meaning.
Interfaces Do Not Require Stored Fields
This is not an interface requirement:
interface Invalid {
Name: string
}
Interfaces describe behavior rather than representation.
If a contract needs observable state, use a property:
interface Named {
property Name: string {
get
}
}
An implementing type can satisfy that property with a field, computed value,
foreign state, register access or another valid implementation strategy.
Important: A stored field named Name does not
automatically satisfy a property requirement named Name.
Receiver Capability Is Part of the Contract
A concrete method has a body, so Sec can infer how much access to
self that method actually requires.
An interface method has no body.
Its receiver requirement must therefore be written explicitly in the interface contract.
Sec distinguishes:
fn
mut fn
static fn
These are contract categories, not cosmetic spellings.
Shared Receiver: fn
Plain fn requires only shared, non-mutating receiver access:
interface Counter {
fn Value() int
}
A caller holding only a shared borrow may call Value().
fn Inspect(counter: ref Counter) void {
let value := counter.Value()
}
A concrete implementation of this requirement may not require mutation of the
complete receiver because doing so would demand more authority than the interface promises.
Mutable Receiver: mut fn
mut fn requires mutable/exclusive receiver access:
interface Counter {
mut fn Increment() void
}
A caller needs compatible mutable authority:
fn Update(counter: ref mut Counter) void {
counter.Increment()
}
A mutable borrow may also call ordinary shared fn members.
Concrete Implementations Infer Receiver Requirements
Concrete implementation methods use ordinary fn syntax:
impl CounterImpl implements Counter {
fn Value() int {
return self.value
}
fn Increment() void {
self.value += 1
}
}
Concrete methods do not repeat mut fn.
The compiler infers receiver requirements from the body and verifies compatibility
with the interface contract.
Concrete behavior may require less authority
interface Observable {
mut fn Observe() bool
}
type Observation struct {
value: bool,
}
impl Observation implements Observable {
fn Observe() bool {
return self.value
}
}
This is valid. The interface allows callers to supply mutable/exclusive access,
while this particular implementation happens not to need all of that authority.
Concrete behavior may not require more authority
interface Readable {
fn Read() int
}
A concrete Read() body that requires mutable receiver access would
not satisfy this contract.
Rule: A concrete receiver requirement may be less demanding
than the interface contract, but never more demanding.
No Whole-Self Consuming Interface Receiver
Ordinary interface methods do not define a receiver mode that consumes the entire
implementing value.
Whole-instance lifetime termination belongs to free and the destruction model.
A method may still mutate owned state or consume an ordinary parameter when its
receiver capability and parameter contract permit that operation.
There is therefore no interface form analogous to a consuming
self receiver.
Receiver Capability and Parameter Ownership Are Independent
Receiver access does not replace ownership contracts on ordinary parameters.
interface Sender {
mut fn Send(-> message: Message) Result[void, SendError]
}
This declaration means two independent things:
- the receiver requires mutable/exclusive access;
message is consumed.
A conforming implementation must preserve both contracts.
The same applies to shared references, mutable references, variadics and other
parameter modes.
Static Interface Requirements
An interface may require a type-level member:
interface Parsable {
static fn Parse(value: string) Result[Self, ParseError]
}
static fn has no instance receiver.
Self refers to the concrete implementing type in this contract.
A static implementation member must match the static requirement.
An instance method does not satisfy a static method requirement, and a static method
does not satisfy an instance method requirement.
The implements List Belongs to the Primary Impl
impl FileReader implements Reader, Closeable {
...
}
Additional same-module implementation fragments use:
impl extends FileReader {
fn Helper() void {
...
}
}
An extension fragment contributes to the same complete implementation surface,
but it does not redeclare conformance.
This is invalid:
impl extends FileReader implements Reader {
...
}
Keeping conformance on the primary implementation gives the type one canonical
source location where its implemented interfaces are declared.
Implementing Multiple Interfaces
A primary implementation may declare several interfaces:
impl Service implements Identified, Named, Described, Resettable {
fn Reset() void {
self._resetCount = 0
}
property Name: string {
get {
return self._name
}
}
fn Describe() string {
return self._name
}
fn Identifier() Identifier {
return self._identifier
}
}
Member declaration order does not need to match the interface list or the order
of requirements inside those interfaces.
The compiler checks the complete merged implementation surface.
Listing the same interface more than once is invalid.
Parameter Names Do Not Define Interface Signature Identity
The interface and implementation may use different parameter names:
interface Processor {
mut fn Process(value: int, limit: uint) bool
}
impl NumberProcessor implements Processor {
fn Process(input: int, maximum: uint) bool {
self._lastValue = input
return maximum > 0
}
}
What must match is the semantic signature: position, type, ownership/borrow mode,
variadic status, result, fallibility and receiver contract.
Renaming parameters does not change the contract.
Interface Values
An interface may be used as an abstraction boundary when a function needs behavior
rather than one concrete representation.
interface Counter {
fn Value() int
mut fn Increment() void
}
Calls through an interface-typed value are restricted by the receiver capabilities
declared in the interface contract.
The concrete implementing representation remains owned by the concrete type.
The interface does not inject fields into that representation.
Borrowing Interface Values
A shared interface borrow may call shared methods:
fn Inspect(counter: ref Counter) void {
let value := counter.Value()
}
It cannot call a mutable requirement:
fn Invalid(counter: ref Counter) void {
counter.Increment() // invalid
}
A mutable interface borrow may call both:
fn Update(counter: ref mut Counter) void {
counter.Increment()
let value := counter.Value()
}
Ordinary borrow lifetimes, exclusivity and ownership rules apply to interface values
just as they do elsewhere.
Property Requirements
Interfaces use properties when the contract requires observable state.
Read-only property
interface Named {
property Name: string {
get
}
}
A conforming implementation may compute or store the value:
type ReadOnlyName struct {
_name: string,
}
impl ReadOnlyName implements Named {
property Name: string {
get {
return self._name
}
}
}
Read-write property
interface Counted {
property Count: CounterValue {
get
set count
}
}
The setter parameter is explicit even though the interface accessor has no body.
Fallible property requirement
interface Configurable {
property Mode: Mode {
get
try set mode ModeError
}
}
The fallible setter's error type is part of the property contract.
A property requirement checks observable behavior; it never requires one particular
backing field.
Property Compatibility
A concrete property must provide at least the access promised by the interface.
A getter-only requirement may be satisfied by a property that also provides a setter:
interface Named {
property Name: string {
get
}
}
impl MutableName implements Named {
property Name: string {
get {
return self._name
}
set incoming {
self._name = incoming
}
}
}
The extra setter does not weaken readable behavior.
The reverse is not true. A getter-only property cannot satisfy an interface that
requires assignment.
A stored field cannot substitute for a required property merely because the names
and types happen to match.
Interface Inheritance
An interface may implement another interface and inherit its requirements:
interface Readable {
fn Read() int
}
interface Writable {
mut fn Write(value: int) void
}
interface ReadWriter implements Readable, Writable {
}
A type implementing ReadWriter must satisfy both inherited contracts:
type MemoryCell struct {
_value: int,
}
impl MemoryCell implements ReadWriter {
fn Read() int {
return self._value
}
fn Write(value: int) void {
self._value = value
}
}
Inherited requirements are transitive.
A type implementing a deeply derived interface must satisfy all requirements
inherited through the complete chain.
Multiple Interface Inheritance
One interface may inherit several contracts:
interface Startable {
mut fn Start() void
}
interface Stoppable {
mut fn Stop() void
}
interface Managed implements Startable, Stoppable, Named {
fn IsRunning() bool
}
An implementing type must satisfy:
Start();
Stop();
- the inherited
Name property;
IsRunning().
Multiple interface inheritance combines contracts.
It does not introduce stored base subobjects or implementation inheritance.
Diamond Inheritance
Reaching the same compatible requirement through several inheritance paths is valid.
interface RootValue {
fn Root() int
}
interface LeftBranch implements RootValue {
}
interface RightBranch implements RootValue {
}
interface Diamond implements LeftBranch, RightBranch {
}
A concrete implementation needs only one compatible Root() member:
impl DiamondValue implements Diamond {
fn Root() int {
return self._root
}
}
Compatible repeated requirements merge semantically rather than requiring duplicate
implementation methods.
Inherited Requirement Conflicts
Multiple inheritance is valid only when repeated requirements are semantically compatible.
This is valid:
interface LeftState {
fn State() int
}
interface RightState {
fn State() int
}
interface CombinedState implements LeftState, RightState {
}
One State() int satisfies both.
This is invalid:
interface IntegerState {
fn State() int
}
interface BooleanState {
fn State() bool
}
interface ConflictingState implements IntegerState, BooleanState {
}
The inherited requirements have the same member identity but incompatible result contracts.
The same rule applies to conflicting property types, accessor requirements,
receiver capabilities and other contract dimensions.
Inheritance cycles
An interface may not inherit from itself directly or indirectly:
interface Left implements Right {
}
interface Right implements Left {
}
The compiler diagnoses the cycle rather than constructing a recursive contract graph.
Generic Interfaces
An interface may declare type parameters:
interface Container[T] {
property Value: T {
get
}
fn Contains(value: T) bool
}
A concrete instantiation such as:
Container[int]
is one concrete interface contract.
Another instantiation:
Container[string]
is a different concrete interface type.
Generic substitution applies to method parameters, results, properties and inherited
interface requirements using the normal generic rules.
Interfaces as Generic Constraints
Interfaces define the capabilities available to constrained generic code:
interface Printable {
fn Print() void
}
fn PrintValue[T: Printable](value: ref T) void {
value.Print()
}
T: Printable does not declare interface conformance.
It requires every substituted concrete T to already conform explicitly.
impl Document implements Printable {
fn Print() void {
...
}
}
Interface inheritance participates in constraint satisfaction.
A type implementing a derived interface also satisfies compatible inherited
interface requirements.
No Method-Level Generic Parameters in Interface Requirements
A generic interface method may use the interface's own type parameters:
interface Sink[T] {
mut fn Write(value: T) void
}
But an interface method does not introduce additional method-level type parameters:
interface Mapper[T] {
fn Map[U](value: T) U // invalid
}
Interface dispatch must have a finite concrete member surface.
Sec has no runtime generic dispatch mechanism that could instantiate arbitrary
new method specializations through an interface value.
Put the variation on the interface itself or use a generic concrete helper function
when the operation needs another independent type parameter.
Compiler-Known Interfaces
The compiler may provide authoritative interface identities that follow the same
explicit conformance model as source-declared interfaces.
The initial compiler-known generic interface is:
interface Iterator[T] {
mut fn Next() Option[T]
}
User code cannot redeclare the compiler-known interface identity.
A concrete type explicitly conforms to it in the normal way.
Iterator[T] has additional compiler-known for-loop
semantics described in the Loops and Iteration chapter.
That special loop behavior does not turn every interface into a runtime-dispatched
generic protocol and does not imply boxing or runtime generic dispatch.
Associated and Nested Declarations
Interfaces may own associated or nested declarations where the corresponding
declaration rules permit them.
Such declarations belong to the interface's namespace.
They do not add stored instance data to implementing types.
The exact syntax and semantics of each nested declaration continue to follow that
declaration kind's own rules.
Interfaces Do Not Define Concrete Representation
Implementing an interface does not add the interface's conceptual state to the
concrete type.
The concrete representation remains defined by the concrete type and its valid
implementation storage rules.
For example:
interface Named {
property Name: string {
get
}
}
can be implemented by:
type StoredName struct {
_name: string,
}
or by a type that computes the name without storing a string field at all.
Rule: Interfaces constrain behavior, not field layout.
Dispatch Model
Calling a concrete method through a concrete type uses the concrete statically
resolved implementation.
Interface-typed values use the interface-value semantics defined by Sec's runtime
interface model when dynamic abstraction is required.
This is separate from generic constraints.
A generic function such as:
fn PrintValue[T: Printable](value: ref T) void {
value.Print()
}
is specialized for concrete T and does not require runtime generic dispatch.
Likewise, compiler-known Iterator[T] conformance used by
for is resolved to a concrete method statically and does not imply
interface-object dispatch for the loop protocol.
Module Ownership
Ordinary impl ownership rules apply to interface conformance.
A concrete type's primary implementation and its impl extends fragments
belong to the type's defining module.
Therefore interface conformance declared by that primary implementation also belongs
to the defining module.
A different module cannot import a foreign type and attach ordinary interface
conformance to it through an out-of-module impl.
This preserves one authoritative implementation and conformance owner for each type.
Diagnostics
Important interface diagnostics include:
- missing required member;
- wrong member name;
- wrong member category;
- static member used for an instance requirement or vice versa;
- wrong parameter count;
- wrong parameter order;
- wrong parameter type;
- wrong shared/mutable/consuming parameter mode;
- wrong result type;
- wrong fallibility contract;
- concrete receiver requirement stronger than the interface permits;
- missing required property;
- field used instead of a required property;
- wrong property type;
- missing required getter or setter;
- incompatible fallible setter contract;
- duplicate interface in an
implements list;
- missing inherited requirement;
- unknown or non-interface inheritance target;
- duplicate inherited interface;
- direct or indirect interface inheritance cycle;
- conflicting inherited method or property requirements;
- duplicate member requirement inside one interface;
- method/property member-name conflict;
implements declared on impl extends;
- stored field requirement declared in an interface;
- ordinary
impl targeting an interface;
- interface implementation attempted outside the concrete type's defining module.
Conformance diagnostics should identify both the interface requirement and the
concrete member that failed to satisfy it when both exist.
error: NumberProcessor does not satisfy Processor.Process
required:
mut fn Process(value: int, limit: uint) bool
found:
fn Process(enabled: bool, count: int) bool
reason:
parameter types and order do not match
For inherited requirements, diagnostics should name the interface that originally
introduced the requirement when practical.
LSP hover and navigation should make it easy to see:
- which interfaces a type explicitly implements;
- where each required member is declared;
- which concrete member satisfies a requirement;
- whether the receiver contract is shared, mutable or static;
- which requirements are inherited;
- generic substitutions for concrete generic interfaces.
Best Practices
- Keep interfaces focused on observable behavior.
- Prefer several small composable interfaces over one broad unrelated contract.
- Use plain
fn when mutation is not part of the caller-visible contract.
- Use
mut fn only when callers genuinely need to provide mutable/exclusive receiver access.
- Keep ordinary parameter ownership explicit independently of receiver capability.
- Use properties rather than stored-field requirements.
- Put the complete
implements list on the primary implementation.
- Use interface inheritance only when the semantic relationship is real.
- Let one compatible concrete member satisfy compatible repeated inherited requirements.
- Use generic interfaces when the contract itself varies by type.
- Use interfaces as generic constraints when compile-time capability is the goal.
- Do not introduce an interface merely to share implementation; interfaces define contracts, not code inheritance.
Summary
- An interface defines a behavioral contract.
- Interface conformance is explicit rather than structural.
- Interfaces do not add stored representation to implementing types.
- An empty interface is valid and still requires explicit conformance.
- Interfaces may require instance methods, mutable methods, static methods, properties and permitted nested declarations.
- Interfaces may not require stored instance fields.
- Use properties when a contract must expose readable or writable state.
- Plain interface
fn requires shared/non-mutating receiver capability.
mut fn requires mutable/exclusive receiver capability.
- Concrete methods use ordinary
fn syntax and infer their receiver needs from the body.
- A concrete receiver requirement may be weaker than the interface contract but never stronger.
- Ordinary interface methods have no whole-self consuming receiver form.
- Receiver capability and ordinary parameter ownership are independent.
- Consuming parameters remain explicit with
->.
static fn requirements have no instance receiver.
- Concrete conformance is declared with
impl Type implements Interface.
- The
implements list belongs to the primary implementation.
impl extends fragments contribute members but do not redeclare interfaces.
- A type may implement several interfaces at once.
- Conformance checks member category, types, parameter modes, result, fallibility, receiver capability and property contracts.
- Parameter names do not form part of interface signature identity.
- Concrete types may expose extra members beyond an interface contract.
- Calls through interface values obey the receiver capability declared by the interface.
- A shared interface borrow cannot call
mut fn.
- A mutable interface borrow may call shared and mutable members.
- Property requirements describe observable access rather than storage.
- A getter-only requirement may be satisfied by a property that also provides compatible write access.
- A field does not satisfy a property requirement merely because its name and type match.
- Interfaces inherit other interfaces with
implements.
- Inherited requirements are transitive.
- Multiple interface inheritance combines contracts rather than stored base representations.
- Compatible repeated inherited requirements merge and need only one concrete implementation.
- Diamond inheritance is valid when repeated requirements are compatible.
- Conflicting inherited requirements are compile-time errors.
- Direct and indirect interface inheritance cycles are invalid.
- Interfaces may be generic.
- Each concrete generic interface instantiation is a concrete interface contract.
- Interfaces may be used as generic constraints.
- A generic constraint requires existing explicit conformance; it does not create conformance.
- Interface methods may use their interface's generic parameters but may not introduce independent method-level generic parameters.
- Compiler-known interfaces follow the same explicit-conformance model.
Iterator[T] is a compiler-known interface with additional loop semantics.
- Generic constraint dispatch and compiler-known iterator lowering do not imply runtime generic dispatch.
- Associated or nested interface declarations do not add instance storage to implementing types.
- Ordinary interface conformance belongs to the implementing type's defining module.