How to avoid writing duplicate accessor functions for mutable and immutable references in Rust?

爷,独闯天下 提交于 2019-11-26 04:28:14

问题


A few times, I\'ve run into the scenario where an accessor method is needed for both mutable and immutable references.

For ~3 lines it isn\'t a problem to duplicate the logic, but when the logic gets more complex, it\'s not nice to copy-paste large blocks of code.

I\'d like to be able to re-use the code for both.

Does Rust provide some way handle this better then copy-pasting code, or using unsafe casts?

e.g.:

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

Here is a more detailed excerpt of a code-base where an immutable return argument was cast to mutable to support both immutable and mutable versions of a function. This uses a wrapped pointer type (ConstP and MutP for immutable and mutable references), but the logic of the function should be clear.

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

回答1:


You don't, really. Recall that T, &T and &mut T are all different types. In that context, your question is the same as asking "How to avoid writing duplicate accessor functions for String and HashMap".

Matthieu M had the right terms "abstract over the mutability":

  • Parameterisation over mutability
  • Dealing with &/&mut in data structures: abstract over mutability or split types?
  • A safe way to reuse the same code for immutable and mutable variants of a function?
  • Abstracting over mutability in Rust
  • "Mutability polymorphism"
  • etc. etc. etc.

The TL;DR is that Rust would likely need to be enhanced with new features to support this. Since no one has succeeded, no one is 100% sure which features those would need to be. The current best guess is higher kinded types (HKT).




回答2:


(playground links to solutions using type parameters and associated types)

In this case &T and &mut T are just two different types. Code that is generic over different types (at both compile-time and run-time) is idiomatically written in Rust using traits. For example, given:

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

suppose we want to provide Bar with a generic accessor for its Foo data member. The accessor should work on both &Bar and &mut Bar appropriately returning &Foo or &mut Foo. So we write a trait FooGetter

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

whose job is to be generic over the particular type of Bar we have. Its Output type will depend on Bar since we want get to sometimes return &Foo and sometimes &mut Foo. Note also that it consumes self of type Self. Since we want get to be generic over &Bar and &mut Bar we need to implement FooGetter for both, so that Self has the appropriate types:

// 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 }
}

Now we can easily use .get() in generic code to obtain & or &mut references to Foo from a &Bar or a &mut Bar (by just requiring T: FooGetter). For example:

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

Note that you can also implement FooGetter for Bar, so that get is generic over &T,&mut T, and T itself (by moving it in). This is actually how the .iter() method is implemented in the standard library, and why it always does "the right thing" independently of the reference-ness of the argument its invoked on.




回答3:


Currently Rust doesn't support abstracting over mutability.

There are some ways this can be achieved, although they aren't ideal:

  • Use a macro to expand the duplicate code, declare the macro and share between both functions - needs to be constructed so it works for mutable and immutable of course.
  • Write the immutable version of the function (to ensure nothing is changed), then write a wrapper function for the mutable version which performs an unsafe cast on the result to make it mutable.

Neither of these are very appealing (a macro is overly verbose and a little less readable, adds some code-bloat), the unsafe is more readable, but it would be nice to avoid since casting from immutable to mutable isn't so nice to have through a code-base.

For now the best option as far as I can see (where copy-pasting code isn't acceptable), is to write an immutable version of the function, then wrap it with a mut version of the function where both inputs and outputs are mutable.

This requires an unsafe cast on the output of the function, so it's not ideal.


Note: it's important to have the immutable function contain the body of the code, since the reverse will allow accidental mutating of what might be immutable input.



来源:https://stackoverflow.com/questions/41436525/how-to-avoid-writing-duplicate-accessor-functions-for-mutable-and-immutable-refe

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!