Skip to content

Opaque Types and Boxed Protocol Types ​

Implementation status: some P (opaque result types) Implemented (Phase 11 GA, fixtures/format/opaque_some_mvp / fixtures/parity/protocol_witness.sy); any P (existential types) Implemented (witness-table dynamic dispatch)

SYX offers two ways of "hiding the concrete type":

  • some P — opaque result type: hidden at compile time; callers can only use it as P
  • any P — existential type: dispatched at runtime through a witness table; the caller gets "some kind of P"

some — Opaque Result Types ​

some hides concrete type information while preserving compile-time type inference:

swift
protocol Shape {
    func area() -> Double
}

class Square: Shape {
    var s: Double
    init(s: Double) { self.s = s }
    func area() -> Double { return s * s }
}

class Circle: Shape {
    var r: Double
    init(r: Double) { self.r = r }
    func area() -> Double { return 3.14159 * r * r }
}

func makeShape() -> some Shape {            // Return type: some Shape (known at compile time)
    return Square(s: 2.0)                   // Actually a Square
}

func main() {
    let shape = makeShape()
    print(shape.area())                     // 4.0 (through the Shape interface)
}
BehaviorDescription
CallerSees some Shape (satisfies the Shape protocol)
InternallyActually a Square, concrete at compile time
Implementation changesSquare → Circle does not break callers (API stability)
PerformanceZero virtual dispatch (static at compile time)

Limit: all return paths of the same function must return the same concrete type. if cond { return A() } else { return B() } is a compile error.

Tests: fixtures/format/opaque_some_mvp · fixtures/parity/protocol_witness.sy

any — Existential Types ​

any is a container for "some kind of P"; dispatch at runtime goes through a witness table:

swift
protocol Shape {
    func area() -> Double
}

class Circle: Shape {
    var r: Double
    init(r: Double) { self.r = r }
    func area() -> Double { return 3.14159 * r * r }
}

func main() {
    let s: any Shape = Circle(r: 2.0)
    print(s.area())                         // 12.56636
}
BehaviorDescription
CallerSees any Shape (existential type)
InternallyActually a Circle, dispatched at runtime through a witness table
Multiple concrete typesThe same variable may hold different implementations such as Square / Circle
PerformanceIndirect dispatch (witness table)
swift
protocol Shape {
    func area() -> Double
}

class Square: Shape {
    var s: Double
    init(s: Double) { self.s = s }
    func area() -> Double { return s * s }
}

class Circle: Shape {
    var r: Double
    init(r: Double) { self.r = r }
    func area() -> Double { return 3.14159 * r * r }
}

func main() {
    let shapes: Array<any Shape> = [
        Square(s: 2.0),
        Circle(r: 3.0)
    ]
    for s in shapes {
        print(s.area())                     // 4.0, 28.27431
    }
}

Test: fixtures/parity/protocol_witness.sy

some vs any ​

Dimensionsome Pany P
Compile-time typePreserved (concrete type)Erased (only P)
Runtime dispatchStatic (no witness)Dynamic (witness table)
Multiple type values❌ (a function must return one type)✅ (Array<any P> may mix)
PerformanceZero virtual dispatchIndirect call
ReflectionVisibleOnly the P interface

Usage Scenarios ​

ScenarioChoose
A function returns a single concrete type, exposing only the protocolsome P
Storing multiple types conforming to Pany P
API stability + performance sensitivesome P
Polymorphic collections / factoriesany P
swift
protocol Shape {
    func area() -> Double
}

class Square: Shape {
    var s: Double
    init(s: Double) { self.s = s }
    func area() -> Double { return s * s }
}

class Circle: Shape {
    var r: Double
    init(r: Double) { self.r = r }
    func area() -> Double { return 3.14159 * r * r }
}

// Factory: returns multiple implementations → `any P`
func makeShape(kind: String) -> any Shape {
    if kind == "circle" { return Circle(r: 1.0) }
    return Square(s: 1.0)
}

// API: returns a single implementation → `some P`
func defaultShape() -> some Shape { return Square(s: 1.0) }

func main() {
    print(makeShape(kind: "circle").area())
    print(defaultShape().area())
}

Casting any P ​

swift
protocol Shape {
    func area() -> Double
}

class Circle: Shape {
    var r: Double
    init(r: Double) { self.r = r }
    func area() -> Double { return 3.14159 * r * r }
}

func main() {
    let s: any Shape = Circle(r: 2.0)
    if let c = s as? Circle {
        print("circle r=\(c.r)")            // "circle r=2.0"
    }
}

Status: runtime as? / as! casting is Implemented (see Chapter 20).

Constraints on some P ​

swift
func make() -> some Equatable {             // Satisfying Equatable is enough
    return 42                                // Actually an Int
}

func makeBox<T: Equatable>(_ v: T) -> some Equatable {
    return v
}

Common Patterns with any P Collections ​

swift
protocol Drawable {
    func draw()
}

class Button: Drawable { func draw() { print("[btn]") } }
class Text: Drawable { func draw() { print("[text]") } }

func main() {
    let ui: Array<any Drawable> = [
        Button(),
        Text(),
        Button()
    ]
    for el in ui { el.draw() }
}

Combining with Generics ​

swift
func makePair<T: Equatable>(_ a: T, _ b: T) -> some Equatable {
    return (a, b)                            // Tuple (T, T)
}

Implementation Details of Existential Types ​

At runtime any P stores:

  • The value itself (concrete type)
  • A witness table (a function pointer for each P method)
swift
protocol Named {
    func name() -> String
}

class User: Named {
    var n: String
    init(name: String) { self.n = name }
    func name() -> String { return self.n }
}

func main() {
    let p: any Named = User(name: "Alice")
    print(p.name())
    // Memory layout: [ value | witness table ptr ]
    // Dispatch: p.name() → witness_table.name(value)
}

Code generation: the CallWitness instruction goes through the witness table; see syx-codegen.

Complete Example ​

swift
protocol Logger {
    func log(_ msg: String)
}

class ConsoleLogger: Logger {
    func log(_ msg: String) { print("[console] \(msg)") }
}

class FileLogger: Logger {
    let path: String
    init(path: String) { self.path = path }
    func log(_ msg: String) { print("[file:\(path)] \(msg)") }
}

// Factory: returns any P (multiple implementations)
func makeLogger(kind: String) -> any Logger {
    if (kind == "file") { return FileLogger(path: "/tmp/log") }
    return ConsoleLogger()
}

// API: returns some P (a single fixed one)
func defaultLogger() -> some Logger {
    return ConsoleLogger()
}

func main() {
    let l1: any Logger = makeLogger(kind: "file")
    let l2 = defaultLogger()
    l1.log("hello")
    l2.log("world")
}

Diagnostics ​

CodeTriggerFix
S004some P returning multiple typesUnify into one concrete type
S010Does not conform to the protocolAdd conformance
P001Syntax errorCheck some / any

Differences from Swift ​

SwiftSYX
some P opaque returnImplemented
any P existential typeImplemented
some P in parameter positionNot implemented (planned)
any P in return positionImplemented
some Collection<Element> associated typesPartially implemented
Full existential self-conformancePartially implemented (basic witness)
Performancesome P zero virtual dispatch; any P indirect

Next chapter → Memory Management and Garbage Collection

See also → Language Reference: Types · Chapter 20, Type Casting · Chapter 23, Protocols · Chapter 24, Generics

SYX · Apache-2.0