如何将接收&Repository的Rust函数转为其Trait方法?避免堆分配
我有如下函数,因迭代器类型过于冗长未使用显式返回类型,仅返回Result<impl Iterator<Item = Commit>>:
pub fn commits(repo: &Repository) -> Result<impl Iterator<Item = Commit>> { let head_ref = repo .head_commit() .context("No HEAD commit found! Does the repository contain commits?")? .id(); let state = ancestors::State::default(); Ok( Ancestors::new(std::iter::once(head_ref), state, |oid, buf| { repo.objects.find_commit_iter(oid, buf) }) .filter_map(|oid| repo.find_object(oid.ok()?).ok()?.try_into_commit().ok()), ) }
由于该函数仅接收Repository的引用作为参数,将其实现为Repository的Trait方法更合理。
我尝试定义如下Trait:
trait CommitIter { fn commits(&self) -> Result<impl Iterator<Item = Commit>>; }
但编译器报错:error[E0562]: impl Trait only allowed in function and inherent method return types, not in trait method return types,我理解这是因为Trait定义阶段编译器无法预知实现的返回类型。
我知道可以用Box包装迭代器(即Result<Box<dyn Iterator<Item = Commit>>>),但希望尽量避免堆分配。
我也试过泛型定义Trait:
trait CommitIter<'a> { fn commits<I>(&self) -> Result<I> where I: Iterator<Item = Commit<'a>>; }
但这会改变commits的语义,使其可返回任意迭代器类型,而我只想为特定类型实现该方法,让编译器自动推导返回类型。
我还尝试了关联类型方案:
trait CommitIter<'a> { type FReturn: Iterator<Item = Commit<'a>>; fn get_commit_iter(&'a self) -> Result<Self::FReturn>; }
但实现时遇到报错:error[E0658]: impl Trait in associated types is unstable:
impl<'a> CommitIter<'a> for Repository { type FReturn = impl Iterator<Item = Commit<'a>>; fn get_commit_iter(&self) -> Result<Self::FReturn> { let head_ref = self .head_commit() .context("No HEAD commit found! Does the repository contain commits?")? .id(); let state = ancestors::State::default(); Ok( Ancestors::new(std::iter::once(head_ref), state, |oid, buf| { self.objects.find_commit_iter(oid, buf) }) .filter_map(|oid| self.find_object(oid.ok()?).ok()?.try_into_commit().ok()), ) } }
根据rust-analyzer,返回的迭代器类型是类似FilterMap<Ancestors<{closure#35}, fn(&oid) -> bool, State>, {closure#36}>的冗长类型,无法显式指定。
如何在不进行堆分配、不写出完整迭代器类型的前提下,将该函数改写为Repository的Trait方法?若无法实现,此类场景的惯用处理方式是什么?
1. 优先选择固有方法(稳定版可用)
如果不需要为多个类型实现该方法,直接将commits作为Repository的固有方法是最简洁的方案,完全支持impl Trait返回类型,无需堆分配,也不用处理冗长类型:
impl Repository { pub fn commits(&self) -> Result<impl Iterator<Item = Commit>> { let head_ref = self .head_commit() .context("No HEAD commit found! Does the repository contain commits?")? .id(); let state = ancestors::State::default(); Ok( Ancestors::new(std::iter::once(head_ref), state, |oid, buf| { self.objects.find_commit_iter(oid, buf) }) .filter_map(|oid| self.find_object(oid.ok()?).ok()?.try_into_commit().ok()), ) } }
2. Nightly版本:使用impl_trait_in_assoc_type特性
若必须使用Trait且能接受Rust nightly版本,开启impl_trait_in_assoc_type特性即可解决关联类型中无法使用impl Trait的问题:
#![feature(impl_trait_in_assoc_type)] trait CommitIter<'a> { type Iter: Iterator<Item = Commit<'a>>; fn commits(&'a self) -> Result<Self::Iter>; } impl<'a> CommitIter<'a> for Repository { type Iter = impl Iterator<Item = Commit<'a>>; fn commits(&self) -> Result<Self::Iter> { // 原函数逻辑不变 let head_ref = self .head_commit() .context("No HEAD commit found! Does the repository contain commits?")? .id(); let state = ancestors::State::default(); Ok( Ancestors::new(std::iter::once(head_ref), state, |oid, buf| { self.objects.find_commit_iter(oid, buf) }) .filter_map(|oid| self.find_object(oid.ok()?).ok()?.try_into_commit().ok()), ) } }
该特性允许在关联类型中使用impl Trait,让编译器自动推导具体的迭代器类型,无需手动写出冗长的组合类型。
3. 稳定版Trait场景:接受堆分配(惯用方案)
如果一定要在稳定版中实现Trait方法,Box<dyn Iterator>是行业惯用方案。虽然存在堆分配,但在大多数迭代场景下,其性能开销可以忽略:
trait CommitIter<'a> { fn commits(&self) -> Result<Box<dyn Iterator<Item = Commit<'a>> + 'a>>; } impl<'a> CommitIter<'a> for Repository { fn commits(&self) -> Result<Box<dyn Iterator<Item = Commit<'a>> + 'a>> { let head_ref = self .head_commit() .context("No HEAD commit found! Does the repository contain commits?")? .id(); let state = ancestors::State::default(); Ok(Box::new( Ancestors::new(std::iter::once(head_ref), state, |oid, buf| { self.objects.find_commit_iter(oid, buf) }) .filter_map(|oid| self.find_object(oid.ok()?).ok()?.try_into_commit().ok()), )) } }
内容的提问来源于stack exchange,提问作者Frederik

