例如对于一些微不足道的事情:
fn do_nothing(x: &i64) -> &i64 {
return x;
}
fn do_nothing(x: &mut i64) -> &mut i64 {
return x;
}
你可以为某些类型 T 做这个泛型,但你必须围绕泛型类型跳舞。有没有什么办法可以根据传递的参数推断出
mut
?
你可以通过使用自己的特质:
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); }