如何在Sorbet中为T::Struct运行时动态定义prop且避免类型检查报错
问题根因
Sorbet 是静态类型检查工具,不会执行你的运行时 register_props 逻辑,所以无法识别动态添加的 prop 属性,自然会认为 MyStruct 的构造函数不需要任何入参,触发参数数量不匹配的报错。
可行解决方案
方案1:补充 RBI 接口文件(最优推荐)
RBI 是 Sorbet 专用的类型接口文件,专门用于给静态检查补充运行时才会定义的类型信息。你可以在项目的 Sorbet RBI 目录下新增
my_struct.rbi文件,手动声明所有你会动态注册的属性:# 这个文件仅用于 Sorbet 静态类型检查,不影响运行时逻辑 class MyStruct < T::Struct prop :foo, T.untyped prop :bar, T.nilable(T.untyped) end该方案优势在于静态检查会完全识别这些属性的存在,既可以校验构造函数入参,也可以校验后续属性调用的正确性,和你写静态
prop的体验完全一致,运行时的动态注册逻辑也不会和RBI声明冲突。方案2:重写构造函数类型签名,适配动态属性
如果你要注册的prop是完全动态的,无法提前枚举写RBI,可以直接在MyStruct里重写initialize的类型签名,允许接收任意关键字参数:
# typed: true require 'sorbet-runtime' class MyStruct < T::Struct MyPropType = T.type_alias { T::Hash[Symbol, Class] } extend T::Sig sig { params(kwargs: T.untyped).void } def initialize(**kwargs) super(**kwargs) end class << self extend T::Sig sig { params(props: MyPropType).void } def register_props(props) props.each do |prop_name, prop_type| prop(prop_name, prop_type) end end end end该方案下Sorbet不会再校验构造函数的入参数量和名称,运行时T::Struct本身还是会校验你传入的key是否已经注册、类型是否匹配,仅会丢失静态检查阶段的参数校验能力。
方案3:调用构造函数时临时绕过静态检查(不推荐)
如果不想修改原有类的实现,可以在每次创建实例时用
T.unsafe绕过检查:my_struct = T.unsafe(MyStruct).new(foo: 'foo', bar: Bar.new)该方案改动最小,但每次调用都要加
T.unsafe,代码冗余度高,也完全丢失了静态检查的能力。
内容的提问来源于stack exchange,提问作者Waseem
相关产品推荐
相关产品推荐

