协议(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
2float.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 位置、签名形状都在满足性键里,可窄不可宽、错位即报。
