当仅T实现Trait时,如何处理&T类型的适配问题?
问题:让泛型函数同时支持引用和非引用可迭代对象
背景代码定义
首先定义了如下Trait:
pub trait DoSomething { fn do_it(value: Self); }
并为部分类型实现该Trait:
impl DoSomething for i32 { // ... 具体实现 } impl<'a> DoSomething for &'a BigStruct { // ... 具体实现 }
单独使用时,小整数按值传递、BigStruct按引用传递都能正常工作,但编写以下泛型函数后出现了问题:
fn do_something_each<Items, E>(items: Items) where Items: IntoIterator<Item = E>, E: DoSomething, { items.into_iter().for_each(|x| E::do_it(x)); }
问题现象
调用do_something_each(&items)(其中items是BigStruct的Vec)可以正常运行,但调用do_something_each(&integers)(其中integers是i32的Vec)时,编译器报错&i32未实现DoSomething。原因是&Vec<T>的IntoIterator返回的迭代器元素类型是&T,而我们仅为i32实现了DoSomething,未给&i32实现。
尝试的方案及冲突
尝试为&T添加 blanket 实现:
impl<'a, T> DoSomething for &'a T { fn do_it(value: &T) { T::do_it(*value) } }
但该实现与项目中另一个必需的 blanket 实现冲突:
impl<T: DoSomethingElse> DoSomething for T { // ... 具体实现 }
当T实现了DoSomethingElse时,&T会同时匹配这两个DoSomething的实现,导致编译器无法确定使用哪一个。
解决方案
可以引入一个辅助Trait来适配值和引用类型,避免直接修改DoSomething的实现导致冲突:
- 定义辅助Trait
CanDoSomething:
pub trait CanDoSomething { fn do_it(self); }
- 为所有实现了
DoSomething的类型实现CanDoSomething:
impl<T: DoSomething> CanDoSomething for T { fn do_it(self) { T::do_it(self); } }
- 为所有实现了
DoSomething的类型的引用实现CanDoSomething:
impl<'a, T: DoSomething> CanDoSomething for &'a T { fn do_it(self) { T::do_it(*self); } }
- 修改
do_something_each的约束,将E: DoSomething替换为E: CanDoSomething:
fn do_something_each<Items, E>(items: Items) where Items: IntoIterator<Item = E>, E: CanDoSomething, { items.into_iter().for_each(|x| x.do_it()); }
这样修改后,无论是传递值类型的迭代器,还是引用类型的迭代器(比如&Vec<i32>或&Vec<BigStruct>),都能正常调用do_something_each,同时不会和原有的DoSomething blanket 实现产生冲突。
另一种可选方案是直接调整泛型函数的约束,利用Deref trait:
fn do_something_each<Items, E>(items: Items) where Items: IntoIterator<Item = E>, E: Deref, E::Target: DoSomething, { items.into_iter().for_each(|x| <E::Target as DoSomething>::do_it(*x)); }
但这种方案仅适用于引用类型,而辅助Trait的方案更通用,还能支持其他可以转换为DoSomething类型的情况。
内容的提问来源于stack exchange,提问作者Emil Sahlén
相关产品推荐
相关产品推荐

