首页 文章

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

提问于
浏览
18

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

对于~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;
    }
}

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

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()
    };
}

3 回答

  • 7

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

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

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

  • 10

    (playground使用type parametersassociated types链接到解决方案)

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

    struct Foo { value: i32 }
    struct Bar { foo: Foo }
    

    假设我们想为 Bar 提供 BarFoo 数据成员的通用访问器 . 访问器应该适用于 &Bar&mut Bar ,适当地返回 &Foo&mut Foo . 所以我们写了一个特质 FooGetter

    trait FooGetter {
        type Output;
        fn get(self) -> Self::Output;
    }
    

    我们的工作是针对特定类型的 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 }
    }
    

    现在,我们可以在通用代码中轻松使用 .get()&Bar&mut Bar 获取 &&mutFoo 的引用(仅需要 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);
    }
    

    请注意,您还可以为 Bar 实现 FooGetter ,以便 get&T&mut TT 本身上是通用的(通过将其移入) . 这实际上是 .iter() 方法在标准库中的实现方式,以及为什么它总是独立于其调用的参数的引用 .

  • -5

    目前Rust不支持抽象可变性 .

    有一些方法可以实现,虽然它们并不理想:

    • 使用宏来扩展重复的代码,声明宏并在两个函数之间共享 - 需要构造,因此它当然适用于可变和不可变 .

    • 编写函数的不可变版本(以确保没有任何更改),然后为可变版本编写包装函数,该函数对结果执行 unsafe 强制转换以使其可变 .

    这些都不是很吸引人(宏过于冗长,可读性稍差,添加了一些代码膨胀), unsafe 更具可读性,但是因为从不可变变为可变而不是很好通过代码库 .

    现在,就我所见,最好的选择(复制粘贴代码是不可接受的),是编写函数的不可变版本,然后用函数的 mut 版本包装它,其中输入和输出都是可变的 .

    这需要在函数的输出上进行 unsafe 强制转换,因此它并不理想 .


    注意:让不可变函数包含代码体是很重要的,因为反向将允许意外改变可能是不可变输入的内容 .

相关问题