Skip to Content

协议(Protocol)

protocol 声明一组成员签名:谁 impl 谁,谁就能以协议类型流动。它是接口契约,也是泛型约束的载体。

声明与实现

protocol Greeter { func Greet() -> string; } struct Tag impl Greeter { public var text: string = "标"; public func Greet() -> string => $"你好,{text}"; }

协议成员只有签名没有方法体。实现方用 impl 子句挂上协议(struct 与 class 均可使用),再逐条提供同形成员。

满足性按完全匹配校验:成员种类、参数个数与类型与修饰符、返回类型全部严格相同(当前版本没有协变逆变)。

协议位赋值与虚派发

协议名可以直接当类型用,装任何实现了它的值:

func Introduce(g: Greeter) -> string => $"听:{g.Greet()}"; val t = new Tag(); print(Introduce(t)); val g: Greeter = t; print(g.Greet()); class DogImpl impl Greeter { public func Greet() -> string => "汪"; } print(Introduce(new DogImpl()));
听:你好,标 你好,标 听:汪

Introduce 只认协议面,不关心背后是 struct 还是 class。协议位的调用按运行时类型分派,属性(prop)同样走虚派发,协议位读到的永远是运行时类型的实现。

除了直接赋值,as 也能把值转成协议位:val w = r as Walker 返回 Walker?,值实现了协议就得引用,没实现得 null。any 里的值同样按实现链判定,o is Walker 与 o as Walker 都可用。is / as / T(v) 的转型分工见类型转换。

Self 与 Clone

Self 在协议内指代「实现方的类型」,是写 Clone 这类契约的关键:

protocol Clone { func Clone(self) -> Self; }

Tag impl Clone 后 Clone() 返回 Tag 自身,泛型代码里 T: Clone 的返回值也不用强转。派生类 override 里 Self 各自绑定到各自类型。

运算符签名

协议可以要求运算符,实现方逐条提供同形 operator 重载。std::Math 的 Numeric / Ordered 是纯运算符协议,自己写一个也是同款:

protocol MoneyOps { operator +(self, other: Self) -> Self; operator ==(self, other: Self) -> bool; } struct Money impl MoneyOps { public var cents: int = 0; public operator +(self, other: Money) -> Money => new Money() { cents: self.cents + other.cents }; public operator ==(self, other: Money) -> bool => self.cents == other.cents; } val a = new Money() { cents: 100 }; val b = new Money() { cents: 50 }; print((a + b).cents); print(a == new Money() { cents: 100 });
150 True

协议内 Self 落到实现方自己的类型。内建标量按内建运算能力隐式满足纯运算符协议:Sum<T> where T: Numeric 里 int、float 全族不用写 impl(见泛型)。

static 成员

协议可以要求 static 成员,impl 方必须以 static 提供。static 成员没有接收者分派:编译期已知具体类型就直调,泛型约束体内 T.成员 在实例化点按具体类型分派:

protocol Lerpable { static pure func Lerp(from: Self, to: Self, t: float) -> Self; } extension float impl Lerpable { public static pure func Lerp(from: float, to: float, t: float) -> float => from + (to - from) * t; } func Blend<T>(a: T, b: T, t: float) where T: Lerpable -> T { return T.Lerp(a, b, t); } print(float.Lerp(0.0, 10.0, 0.5)); print(Blend(1.0, 3.0, 0.5));
5 2

float.Lerp 直调到实现方的 static 方法,Blend 里的 T.Lerp 在 Blend(1.0, ...) 实例化时绑定为 float 的实现。static 协议成员常经扩展挂到标量与内建类型上,这也让标量得以参与泛型约束,见扩展。

fail 集:可窄不可宽

协议成员声明的 fail 集进满足性键:impl 端的 fail 集不得超出声明,收窄放行、等集放行、不允许扩宽。安全方向很直接:调用方持协议静态类型,按声明的 fail 集写消费,虚分派目标实际可抛的不得超出这个面。

error Boom(); protocol Risky { func Go() fail Boom -> int; } struct Safe impl Risky { public func Go() -> int => 1; } val s = new Safe(); print(s.Go());
1

收窄可以窄到不声明 fail:Safe 的直接类型位调用从此没有失败面。持协议位(Risky)的调用点仍按声明的 fail Boom 消费。扩宽一侧(比如 impl 写 fail Boom, Other)在 impl 检查处会被编译器拒绝。

impl 沿继承链传播

基类 impl 的协议,派生类自动满足,协议位直接可用派生类型:

protocol Named2 { func Name() -> string; } class Base2 impl Named2 { public virtual func Name() -> string => "基础"; } class Derived2 : Base2 { public override func Name() -> string => "派生"; } func Show(n: Named2) -> string => n.Name(); print(Show(new Derived2()));
派生

Derived2 没写 impl,Show 照收。

派生类也可以显式 re-impl 覆写基类的实现体,re-impl 的满足性校验把继承链成员一并计入「自带成员」:基类的 public virtual 同形成员算数,只有链上确无同形成员时才报错。协议/虚分派按接收者运行时类型取链上最近提供的实现体。

泛型协议

协议带类型参数,impl 时显式实例化:

protocol Holder<T> { init(); func Get() -> T; } class IntBox impl Holder<int> { public var v: int = 0; init() { v = 0; } public func Get() -> int { return v; } } func Pick<T>() where T: Holder<int> -> int { return new T().Get(); } func main() -> int { val b = new IntBox() { v: 9 }; print(b.Get()); print(Pick<IntBox>()); return 0; }
9 0
  • 协议体的 T 就是类型参数,成员签名随便用,协议同样可以带 where 约束。
  • impl 必须写全实例化(impl Holder<int>),init(); 构造签名要求 impl 方提供对应构造器。
  • 泛型约束 where T: Holder<int> 之后,约束体内的 T 具备协议面,且 new T() 合法,Pick<IntBox>() 拿到的是全新实例。

协议能要求什么

  • 方法(func)与属性(prop)签名。
  • 构造签名(init();):impl 方必须提供对应构造器。协议带 init 签名时,where T: P 的泛型体内 new T(args) 合法。
  • 运算符(operator +(self, other: Self) -> Self;):内建标量按内建运算能力隐式满足:std::Math 的 Numeric(四则 + 判等)与 Ordered(加序比较)就是纯运算符协议,见泛型。
  • static 成员(static pure func Lerp(...)):impl 方以 static 提供,泛型约束体内 T.成员 按实例化类型分派,见上文;static prop 同收签名形,impl 方以 static 属性提供。
  • 事件签名(event Clicked(power: int);)与事件属性签名(event prop Level: int;):impl 方提供同名同载荷事件即满足(载荷逐位类型相等,同名普通 prop 不能冒充 event prop)。event prop 签名必须是裸签名形,不可以带访问器体、管线段或初始化器。
  • 基协议:protocol Ordered : Numeric 的 impl 方须同时满足 Numeric 全部要求。
protocol Clickable { event Clicked(power: int); func Fire(); } class Button impl Clickable { public event Clicked(power: int); public func Fire() { emit Clicked(3); } } val c: Clickable = new Button(); c.Clicked ~> (p) => { print($"点 {p}"); }; c.Fire();
点 3

注意

  • impl 满足性沿继承链传播:基类 impl 的协议,派生类自动满足;
  • 派生类可显式 re-impl 覆写基类提供的实现;
  • 协议成员签名可带 pure / async 修饰,表达契约意图;
  • 协议成员默认可见性与类型成员一致,实现方的同名成员写 public;
  • impl 端成员的 fail 集、static 位置、签名形状都在满足性键里,可窄不可宽、错位即报。
最后更新于 2026年10月9日