你怎么能使一个函数通用,不管它的参数是否是可变借用的?

问题描述 投票:0回答:1

例如对于一些微不足道的事情:

fn do_nothing(x: &i64) -> &i64 {
  return x;
}
fn do_nothing(x: &mut i64) -> &mut i64 {
  return x;
}

你可以为某些类型 T 做这个泛型,但你必须围绕泛型类型跳舞。有没有什么办法可以根据传递的参数推断出

mut

rust
1个回答
0
投票

你可以通过使用自己的特质:

trait MaybeMutRef {}

impl<T: ?Sized> MaybeMutRef for &T {}
impl<T: ?Sized> MaybeMutRef for &mut T {}

fn do_nothing<T: MaybeMutRef>(x: T) -> T {
    x
}

这个特性本身的价值并没有那么大的用处。如果你需要改变基于可变性的行为,你可以通过向特征添加一些成员来实现。

trait MaybeMutRef {
    type Target: ?Sized;
    
    fn get(&self) -> &Self::Target;
    fn get_mut(&mut self) -> Option<&mut Self::Target>;
}

impl<T: ?Sized> MaybeMutRef for &T {
    type Target = T;
    
    fn get(&self) -> &Self::Target {
        self
    }
    
    fn get_mut(&mut self) -> Option<&mut Self::Target> {
        None
    }
}

impl<T: ?Sized> MaybeMutRef for &mut T {
    type Target = T;
    
    fn get(&self) -> &Self::Target {
        self
    }
    
    fn get_mut(&mut self) -> Option<&mut Self::Target> {
        Some(self)
    }
}

fn do_nothing<T: MaybeMutRef>(mut x: T) {
    if let Some(x) = x.get_mut() {
        // Mutable reference, x is a &mut T::Target
    } else {
        let x = x.get();
        // Immutable reference, x is a &T::Target
    }
}

编译器应该能够内联

get*
调用,从而为您消除死分支。

请注意,这是一个有点奇怪的模式,我会小心地在所有地方使用它。但是,如果您有一个算法,其中不可变和可变情况的逻辑几乎相同,除了您可以根据参数的可变性设定条件的一两个细节之外,这种方法可能很有用。

如果引用需要指向特定类型的引用对象,您可以像往常一样使用通用约束:

fn do_nothing<T: MaybeMutRef<Target=i64>>(mut x: T) { todo!() }

作为最后的建议,我认为这不是您想要在 API 中公开的那种函数签名。相反,考虑将 trait 和 function 设为私有细节并拥有更多 Rust-y 公共函数,如下所示:

fn do_nothing_impl<T: MaybeMutRef<Target=i64>>(mut x: T) { todo!() }

pub fn do_nothing(x: &i64) { do_nothing_impl(x); }
pub fn do_nothing_mut(x: &mut i64) { do_nothing_impl(x); }
© www.soinside.com 2019 - 2024. All rights reserved.