为函数实现Trait时遇到无约束类型参数(E0207)的疑问
咱们先把问题里的代码摆清楚,方便分析:
首先是定义的Trait:
trait Trait { type T; fn fun(&self, x: Self::T) -> Self::T; }
第一个能正常编译的实现:
impl<F: Fn() -> H, H> Trait for F { type T = H; fn fun(&self, _x: H) -> H { (self)() } }
第二个编译失败的实现:
impl<F: Fn(H) -> H, H> Trait for F { type T = H; fn fun(&self, x: H) -> H { (self)(x) } }
报错信息是:
error[E0207]: the type parameter
His not constrained by the impl trait, self type, or predicates
为什么第一个能过,第二个不行?
核心原因在于编译器能不能从F唯一确定H的类型:
第一个实现的逻辑
在第一个impl里,F是一个“无参、返回H”的函数类型。对于任何具体的F(比如fn() -> i32),H就是F的返回值类型——这个类型是完全由F本身决定的,编译器不会有任何歧义。哪怕是泛型函数fn<T>() -> T,编译器在单态化的时候,也会为每个具体的T生成独立的impl,每个T对应唯一的H,所以H是被F约束住的,符合Rust的规则。
第二个实现的问题
到了第二个impl,F是“接受H、返回H”的函数类型。这时候问题来了:编译器无法从F唯一确定H。举个极端点的例子,假设我们有一个泛型函数:
fn identity<T>(x: T) -> T { x }
这个函数的类型是fn<T>(T) -> T,它可以实现Fn(H) -> H对于任意H(i32、String、bool等等)。这时候编译器就懵了:到底该为这个F选择哪个H来实现Trait?是i32对应的impl,还是String对应的?没法确定,所以就抛出了E0207错误,告诉你H是未约束的。
哪怕是具体的非泛型函数,比如fn add_one(x: i32) -> i32 { x + 1 },虽然我们知道H只能是i32,但Rust的impl检查是在泛型层面进行的,它不会考虑具体的实例,只看impl的定义是否存在歧义可能。只要存在一种F对应多个H的情况,这个impl就会被判定为不合法。
怎么解决这个问题?
如果你的需求就是让所有“输入输出同类型”的函数实现Trait,可以把Trait改成带类型参数的版本,这样H就会被Trait的类型参数约束:
trait Trait<T> { fn fun(&self, x: T) -> T; } impl<F: Fn(H) -> H, H> Trait<H> for F { fn fun(&self, x: H) -> H { (self)(x) } }
这样编译器就能明确:每个H对应一个独立的Trait<H>实现,不会有歧义。
如果一定要用关联类型的Trait,可能需要引入额外的trait来关联函数和它的输入输出类型,不过这种写法会复杂一些,不如类型参数版本直接。
备注:内容来源于stack exchange,提问作者Michał Trybus

