Language reference

Interfaces

interface declarations describe a contract that other types may implement. Interfaces are reference types; they emit as abstract CLR interfaces and cannot be instantiated directly.

interface ILogger {
    func Log(message: string) -> ()
}

Interfaces may be declared at the top level, inside namespaces, or nested inside other types. Like classes, they support the same set of member declarations (methods, properties, indexers, and nested types). Instance members are abstract requirements by default, but supplying a body for a method or accessor turns it into a default implementation emitted directly on the interface.

Static members, by contrast, must provide a body and emit as real static members on the interface type; implementing types never participate in their implementation or override process. When an interface member uses accessors, a bare ; accessor denotes an unimplemented accessor requirement (get;/set;).

Member accessibility

Interface members default to public, including members with default bodies. Explicit public, private, protected, internal, protected internal, and private protected access modifiers retain their ordinary meaning, following C#. Private function members must have implementations and cannot be abstract or virtual. Instance default implementations are implicitly virtual unless private or sealed. These rules apply to methods and property, indexer, and event accessors.

Implicit interface implementation requires public members on both the interface and the implementing type, including required property accessors. Non-public requirements use explicit interface implementations. Private default helpers are not implementation requirements and do not become class virtual slots.

Base interfaces

An interface may inherit from other interfaces by listing them after a colon. Every base must be an interface. The base list is resolved in the declared interface's scope, including its own generic type parameters, also for nested declarations. For example, interface Child<T> : Parent<T> {} passes Child's T to Parent.

interface IAsyncLogger : ILogger, IDisposable {}

Implementing interfaces

Classes, structs, and union carriers implement interfaces by listing them in their base list. The optional class base (if any) must appear first, followed by one or more interfaces. Structs and unions accept interfaces only. Implementing types must provide members whose signatures match every required interface member—name, parameter count, parameter types (including by-reference modifiers), and return type must align.

class FileLogger : ILogger, IDisposable {
    func Dispose() -> () { /* release resources */ }

    func Log(message: string) -> () {
        Console.WriteLine(message)
    }
}

For a union, the interface is implemented by the carrier rather than by each generated case type:

union LogFailure: ILogger {
    case Unavailable

    func Log(message: string) -> () {
        Console.WriteLine(message)
    }
}

An explicit interface implementation qualifies the member name with the interface type: ILogger.Log. Explicit members are always instance members, ignore virtual and override, and are accessible only through the interface.

The qualifier must name an interface (RAV0313), the containing type must list that interface (RAV0314), and the interface must contain a member with the same name and signature (RAV0315).

class QuietLogger : ILogger {
    func ILogger.Log(message: string) {
        Console.WriteLine("[quiet] " + message)
    }
}
let logger = QuietLogger()
logger.Log("hi")              // error: member not found
(logger :> ILogger).Log("hi") // ok

Reference-only variance conversions

Generic interface out and in annotations admit identity or reference conversions between the corresponding type arguments. Boxing is not a variance conversion: Producer<int> cannot become Producer<object>, and Consumer<object> cannot become Consumer<int>. Identical value arguments remain valid. An unconstrained type parameter does not establish the required reference conversion.

Nullable reference arguments obey the same variance direction while preserving nullability. Producer<string?> can widen to Producer<object?>, and Consumer<object?> can narrow to Consumer<string?>. Variance cannot remove a possible null: Producer<string?> cannot implicitly become Producer<object>, and Consumer<object> cannot become Consumer<string?>. Nullable value types do not become reference arguments through this rule. Runtime generic arguments and emitted interface variance annotations are unchanged.