Swift向泛型函数传入协议实现类型触发段错误排查
问题现象
- 运行Swift自定义函子(Functor)相关代码时触发段错误,运行环境为iPad端ReplIt平台、Swift Playgrounds,无Mac端Xcode设备。
- 直接对
Array、Optional实例调用自定义fmap方法可正常执行,但将实例传入带Funct协议约束的泛型函数usesFunct时,ReplIt抛出segmentation fault: (core dumped)错误,Swift Playgrounds直接提示代码存在错误。 - 复现代码如下:
public protocol Funct { associatedtype Element func fmap<B>(_ fn: (Self.Element) -> B) -> Self where Self.Element == B } extension Array: Funct { public func fmap<B>(_ fn: (Element) -> B) -> Array<B> { return self.map(fn) } } print("Hello it: \([1,3,4,5].fmap { it in "hi \(it + 10)" })") // 直接调用可正常运行 extension Optional: Funct { public func fmap<B>(_ fn: (Wrapped) -> B) -> Optional<B> { return self.map(fn) } } // ---------- 直接调用Optional的fmap可正常运行 -------------------- let huh: Int? = nil let yep: Int? = 9000 print("nope: \(huh.fmap { it in "if its here, it's \(it + 1000)" } as Any)") print("yep: \(yep.fmap { it in "if its here, it's \(it + 1000)" } as Any)") // -------------- 直接调用测试结束 ------------------- func add1000(_ x: Int) -> Int { x + 1000 } // -------------- 泛型调用触发错误 ---------------- func usesFunct<F: Funct>(_ functr: F) -> F where F.Element == Int { return functr.fmap(add1000) // Playgrounds标记此处为错误点 } let workplease: Int? = usesFunct(Optional.some(100)) // 传入Array实例同样报错:let workplease: [Int] = usesFunct([1,2,3]) print(workplease as Any)
问题根因
段错误本质是Swift编译器在泛型上下文做协议类型检查时触发了内部崩溃,根源是Funct协议的定义存在两处核心逻辑错误,不符合函子类型的语义要求:
- 错误的返回值声明:协议中
fmap声明返回值为Self,但Self代表的是当前已固定元素类型的容器自身(比如Array<Int>遵守协议时Self就是Array<Int>),而fmap的语义是允许转换元素类型、返回同结构但元素类型为B的容器(比如Int转String时返回Array<String>),这和返回Self的要求天然矛盾。 - 错误的泛型约束:协议中给
fmap添加了where Self.Element == B的约束,直接强制fmap只能接收「输入输出元素类型完全一致」的转换函数,完全违背了fmap支持元素类型转换的设计目标。
之前直接在具体类型实例上调用fmap能正常运行,是因为Swift编译器在这种场景下会直接调用具体类型的方法实现,不会走协议见证表的校验逻辑,不会触发类型检查的矛盾;但在泛型函数中通过协议约束调用fmap时,编译器需要按照协议声明做严格的类型匹配、生成泛型特化代码,协议声明的语义矛盾直接让类型检查器异常崩溃,最终抛出段错误。
修复方案
Swift目前没有原生支持高阶类型,要正确定义函子协议,需要新增一个关联类型用来表示「装载B类型元素的同结构容器类型」,移除错误的约束和返回值声明,修正后的代码如下:
public protocol Funct { associatedtype Element // 声明同结构容器映射B类型后的对应类型 associatedtype Mapped<B> func fmap<B>(_ fn: (Element) -> B) -> Mapped<B> } extension Array: Funct { public typealias Mapped<B> = Array<B> public func fmap<B>(_ fn: (Element) -> B) -> Array<B> { self.map(fn) } } extension Optional: Funct { public typealias Mapped<B> = Optional<B> public func fmap<B>(_ fn: (Wrapped) -> B) -> Optional<B> { self.map(fn) } } // 测试直接调用 print("Hello it: \([1,3,4,5].fmap { it in "hi \(it + 10)" })") let huh: Int? = nil let yep: Int? = 9000 print("nope: \(huh.fmap { it in "if its here, it's \(it + 1000)" } as Any)") print("yep: \(yep.fmap { it in "if its here, it's \(it + 1000)" } as Any)") func add1000(_ x: Int) -> Int { x + 1000 } // 修正泛型函数的返回值类型为映射后的容器类型 func usesFunct<F: Funct>(_ functr: F) -> F.Mapped<Int> where F.Element == Int { return functr.fmap(add1000) } // 测试泛型调用 let workOptional: Int? = usesFunct(Optional.some(100)) let workArray: [Int] = usesFunct([1,2,3]) print(workOptional as Any) // 输出 Optional(1100) print(workArray as Any) // 输出 [1100, 1101, 1102]
修正后的代码完全符合类型系统要求,不会触发编译器崩溃,在ReplIt和Swift Playgrounds上均可正常运行。
内容的提问来源于stack exchange,提问作者GHvW
相关产品推荐
相关产品推荐

