将函数参数的impl Trait改为泛型是否会构成破坏性变更?
《Rust参考手册》中有如下说明:
注意:对于函数参数,泛型类型参数与impl Trait并非完全等价。使用<T: Trait>这类泛型参数时,调用者可在调用处通过GenericArgs显式指定T的泛型实参,例如
foo::<usize>(1)。若函数存在任意impl Trait类型的参数,则调用者无法为该函数提供任何泛型实参,包括返回类型或常量泛型的泛型实参。
因此,将函数签名在二者间切换会对调用者构成破坏性变更。
但按照这段说明,只要函数有至少一个impl Trait参数,调用者就没法指定任何泛型实参。那把impl Trait改成<T: Trait>,难道不只是让调用者多了显式指定泛型实参的能力吗?既然泛型本来就能被推导,这应该不会破坏现有的类型推导才对。
编辑说明:假设每个impl Trait都被改为不同且唯一的泛型类型(不与其他impl Trait或原有泛型重叠)。违反此假设显然会导致破坏性变更,感谢@啊鹿Dizzyi的指出。
以下是几种常见的变更场景,逐一分析是否会破坏下游代码:
- 仅修改单个impl Trait
// 修改前 pub fn foo(_: impl Trait) {} // 修改后 pub fn foo<T: Trait>(_: T) {}
这个变更不会破坏现有代码。原调用方式(如foo(符合Trait要求的值))完全可以正常工作,泛型T会被自动推导。唯一的变化是调用者现在可以显式指定泛型实参(如foo::<具体类型>(值)),这是新增的能力,不会影响原有调用逻辑。
- 存在多个impl Trait,但仅修改其中部分
// 修改前 pub fn foo(_: impl Trait1, _: impl Trait2, _: impl Trait3) {} // 修改后 pub fn foo<T1: Trait1, T2: Trait2>(_: T1, _: T2, _: impl Trait3) {}
这个变更不会破坏现有代码。原调用方式依然有效,新增的泛型T1、T2会被自动推导。调用者现在可以选择显式指定T1、T2的类型,但这属于新增能力,不会影响原有调用。需要注意的是,由于函数仍保留一个impl Trait参数,调用者依然无法指定其他泛型(如返回类型泛型),但这与原有的限制一致,不会造成破坏。
- 存在多个impl Trait,修改全部
// 修改前 pub fn foo(_: impl Trait1, _: impl Trait2, _: impl Trait3) {} // 修改后 pub fn foo<T1: Trait1, T2: Trait2, T3: Trait3>(_: T1, _: T2, _: T3) {}
这个变更不会破坏现有代码。原调用方式可以正常工作,所有泛型都会被自动推导。调用者现在可以显式指定任意数量的泛型实参,这是新增的能力,不会对原有调用产生负面影响。
- 已有部分泛型类型,修改部分impl Trait,但保留至少一个
// 修改前 pub fn foo<T1: Trait1>(_: T1, _: impl Trait2, _: impl Trait3) {} // 修改后 pub fn foo<T1: Trait1, T2: Trait2>(_: T1, _: T2, _: impl Trait3) {}
这个变更不会破坏现有代码。原调用方式(包括之前可能显式指定T1的调用,如foo::<T1具体类型>(值1, 值2, 值3))依然有效,新增的T2会被自动推导。调用者现在可以额外指定T2的类型,但这属于新增能力,不影响原有逻辑。同时,由于仍保留一个impl Trait参数,调用者依然无法指定其他泛型,这与原限制一致,不会造成破坏。
- 已有部分泛型类型,修改全部impl Trait
// 修改前 pub fn foo<T1: Trait1>(_: T1, _: impl Trait2, _: impl Trait3) {} // 修改后 pub fn foo<T1: Trait1, T2: Trait2, T3: Trait3>(_: T1, _: T2, _: T3) {}
这个变更不会破坏现有代码。原调用方式(包括显式指定T1的调用)都能正常工作,新增的T2、T3会被自动推导。调用者现在可以显式指定T1、T2、T3的任意组合,这是新增的能力,不会影响原有调用。
内容的提问来源于stack exchange,提问作者Aleksander Krauze

