rust 你怎样才能使一个函数通用于它的参数是否被借用?

k4emjkb1  于 2023-03-08  发布在  其他
关注(0)|答案(1)|浏览(88)

为了一些琐碎的事情,比如:

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

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

你可以让这个泛型用于某个类型T,但是你必须绕过泛型类型,有没有什么方法可以根据传递的参数来推断mut

gkn4icbw

gkn4icbw1#

你可以利用你自己的特点:

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本身的值并不是很有用,如果你需要改变基于可变性的行为,你可以通过向trait添加一些成员来实现。

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和函数作为私有细节,并使用更多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); }

相关问题