如何避免在Rust中为可变和不可变引用编写重复的访问器函数?

ide*_*n42 22 reference immutability rust

有几次,我遇到了可变和不可变引用都需要访问器方法的场景.

对于~3行,复制逻辑不是问题,但是当逻辑变得更复杂时,复制粘贴大块代码并不好.

我希望能够重新使用这两个代码.

Rust是否提供了一些方法来处理这个更好的复制粘贴代码或使用unsafe强制转换?

例如:

impl MyStruct {
    pub fn get_foo(&self) -> &Bar {
        // ~20 lines of code
        // --- snip ---
        return bar;
    }
    pub fn get_foo_mut(&mut self) -> &mut Bar {
        // ~20 lines of code
        // (exactly matching previous code except `bar` is mutable)
        // --- snip ---
        return bar;
    }
}
Run Code Online (Sandbox Code Playgroud)

下面是一个代码库的更详细的摘录,其中一个不可变的返回参数被强制转换为可变的,以支持函数的不可变和可变版本.它使用包装指针类型(ConstP以及MutP用于不可变和可变引用),但函数的逻辑应该是清楚的.

pub fn face_vert_share_loop<V, F>(f: F, v: V) -> LoopConstP
    where V: Into<VertConstP>,
          F: Into<FaceConstP>
{
    into_expand!(f, v);

    let l_first = f.l_first.as_const();
    let mut l_iter = l_first;
    loop {
        if l_iter.v == v {
            return l_iter;
        }

        l_iter = l_iter.next.as_const();
        if l_iter == l_first {
            break;
        }
    }

    return null_const();
}
pub fn face_vert_share_loop_mut(f: FaceMutP, v: VertMutP) -> LoopMutP {
    let l = face_vert_share_loop(f, v);
    return unsafe {
        // Evil! but what are the alternatives?
        // Perform an unsafe `const` to `mut` cast :(
        // While in general this should be avoided,
        // its 'OK' in this case since input is also mutable.
        l.as_mut()
    };
}
Run Code Online (Sandbox Code Playgroud)

She*_*ter 11

你没有,真的.回想一下T,&T并且&mut T都是不同的类型.在这种情况下,您的问题与询问"如何避免为String和编写重复的访问器函数HashMap"相同.

Matthieu M有正确的术语"抽象的可变性":

TL; DR是Rust可能需要通过新功能进行增强以支持这一点.由于没有人成功,没有人100%确定需要哪些功能.目前最好的猜测是更高的kinded类型(HKT).

  • 但是如果我绝对必须为 String 和 HashMap 编写访问器函数,我会使用泛型 - 这不是泛型的全部意义吗?想要有一个跨类型工作的结构/函数?看来必须有某种方法来避免代码重复;对我来说,接受如此严重违反 DRY 原则的行为让我感到不太舒服。 (6认同)

gnz*_*lbg 8

(playground使用类型参数相关类型链接到解决方案)

在这种情况下&T,&mut T只是两种不同的类型.在不同类型(在编译时和运行时)通用的代码是使用特征在Rust中以惯用方式编写的.例如,给定:

struct Foo { value: i32 }
struct Bar { foo: Foo }
Run Code Online (Sandbox Code Playgroud)

假设我们想Bar为其Foo数据成员提供通用访问器.访问者应该在两者上工作&Bar&mut Bar适当地返回&Foo&mut Foo.所以我们写了一个特质FooGetter

trait FooGetter {
    type Output;
    fn get(self) -> Self::Output;
}
Run Code Online (Sandbox Code Playgroud)

他们的工作是对Bar我们所拥有的特定类型具有通用性.它的Output类型将取决于Bar因为我们希望get有时回&Foo有时&mut Foo.还要注意它消耗self的类型Self.因为我们想要get通用&Bar,&mut Bar我们需要FooGetter为两者实现,所以它Self具有适当的类型:

// FooGetter::Self == &Bar
impl<'a> FooGetter for &'a Bar {
    type Output = &'a Foo;
    fn get(self) -> Self::Output { & self.foo }
}

// FooGetter::Self == &mut Bar
impl<'a> FooGetter for &'a mut Bar {
    type Output = &'a mut Foo;
    fn get(mut self) -> Self::Output { &mut self.foo }
}
Run Code Online (Sandbox Code Playgroud)

现在,我们可以轻松地使用.get()通用代码来获取&&mut引用Fooa &Bar或a &mut Bar(仅需要T: FooGetter).例如:

// exemplary generic function:
fn foo<T: FooGetter>(t: T) -> <T as FooGetter>::Output {
    t.get() 
}

fn main() {
    let x = Bar { foo: Foo {value: 2} };
    let mut y = Bar { foo: Foo {value: 2} };

    foo(&mut y).value = 3;
    println!("{} {}\n", foo(&x).value, foo(&mut y).value);
}
Run Code Online (Sandbox Code Playgroud)

请注意,您也可以实现FooGetterBar,所以get是在通用的&T,&mut TT本身(在移动它).这实际上是如何.iter()在标准库中实现该方法,以及为什么它总是独立于其调用的参数的引用来做"正确的事".


Ema*_*oun 5

您可以使用板条duplicate

use duplicate::duplicate_item;

impl MyStruct {
  #[duplicate_item(
    get_foo         self        return_type;
    [get_foo]       [&self]     [&Bar];
    [get_foo_mut]   [&mut self] [&mut Bar]
  )]
  pub fn get_foo(self) -> return_type {
    // ~20 lines of code
    // --- snip ---
    return bar;
  }
}
Run Code Online (Sandbox Code Playgroud)

这将扩展到您的第一个示例。但是,通常您可能会在代码中使用各种调用的常量/可变版本。因此,这是对如何编写第二个示例的猜测(必须对命名进行一些猜测):

use duplicate::duplicate_item;
#[duplicate_item(
  face_vert_share_loop        VertConstP    FaceConstP    LoopConstP    as_const    null_const;
  [face_vert_share_loop]      [VertConstP]  [FaceConstP]  [LoopConstP]  [as_const]  [null_const];
  [face_vert_share_loop_mut]  [VertMutP]    [FaceMutP]    [LoopMutP]    [as_mut]    [null_mut];
)]
pub fn face_vert_share_loop<V, F>(f: F, v: V) -> LoopConstP
    where V: Into<VertConstP>,
          F: Into<FaceConstP>
{
    into_expand!(f, v);
    
    let l_first = f.l_first.as_const();
    let mut l_iter = l_first;
    loop {
        if l_iter.v == v {
            return l_iter;
        }
        
        l_iter = l_iter.next.as_const();
        if l_iter == l_first {
            break;
        }
    }
    
    return null_const();
}
Run Code Online (Sandbox Code Playgroud)

它将扩展到:

pub fn face_vert_share_loop<V, F>(f: F, v: V) -> LoopConstP
where
    V: Into<VertConstP>,
    F: Into<FaceConstP>,
{
    into_expand!(f, v);
    let l_first = f.l_first.as_const();
    let mut l_iter = l_first;
    loop {
        if l_iter.v == v {
            return l_iter;
        }
        l_iter = l_iter.next.as_const();
        if l_iter == l_first {
            break;
        }
    }
    return null_const();
}
pub fn face_vert_share_loop_mut<V, F>(f: F, v: V) -> LoopMutP
where
    V: Into<VertMutP>,
    F: Into<FaceMutP>,
{
    into_expand!(f, v);
    let l_first = f.l_first.as_mut();
    let mut l_iter = l_first;
    loop {
        if l_iter.v == v {
            return l_iter;
        }
        l_iter = l_iter.next.as_mut();
        if l_iter == l_first {
            break;
        }
    }
    return null_mut();
}
Run Code Online (Sandbox Code Playgroud)