2

I've been adapting some old code I wrote, one of them had the following (simplified):

pub fn a(x: &mut i32) {
    for i in 0..10 {
        b(x);
    }
}

pub fn b(_x: &mut i32) {

}

which worked fine, even though &mut i32 isn't Copy.

I wanted to restrict what methods could be called on the underlying type, (as instead of &mut i32 I had something along the lines of &mut Vec<...>), so I created a wrapper type over the mutable reference:

#[derive(Debug)]
pub struct I32RefMut<'a>(&'a mut i32);

And I attempted to rewrite a and b using this wrapper as follows:

pub fn a2(x: I32RefMut) {
    for _i in 0..10 {
        b2(x);
    }
}

pub fn b2(_x: I32RefMut) {

}

This gives the following error

17 | pub fn a2(x: I32RefMut) {
   |           - move occurs because `x` has type `I32RefMut<'_>`, which does not implement the `Copy` trait
18 |     for _i in 0..10 {
19 |         b2(x);
   |            ^ value moved here, in previous iteration of loop

Playground link

Which is understandable, as x gets moved into b2 on the first iteration of the loop.

Unfortunately I cannot implement Clone nor Copy, as there may only be 1 mutable reference to the object at a time.

My question is how does &mut i32 work around this and how can I implement this workaround (or similar) on my type I32RefMut.

If possible I'd like to avoid unsafe code as much as possible, such as using #[repr(transparent)] struct I32Wrapper(i32) and then transmuting &mut i32 to &mut I32Wrapper, unless a safe wrapper of this type of operation exists already.

EDIT:

Found a "hack" solution, but I'm not very happy about how it looks, so I'll leave the question open. If no other solutions are found, I'll post it as an answer.

If the call to b2 is changed to b2( I32RefMut(x.0) ), then it successfully compiles. This however, cannot be generalised to a function as such:

impl<'a> I32RefMut<'a> {
    pub fn my_clone<'b: 'a>(&'b mut self) -> I32RefMut<'b> {
        I32RefMut( self.0 )
    }
}

As when we try to call it the compiler tells us we can't borrow x mutably twice.

As this wrapper type is supposed to be defined in a library, I cannot expose it's internal reference, as the whole point of the wrapper was to restrain what the user can call on the reference.

Filipe Rodrigues
  • 1,843
  • 2
  • 12
  • 21
  • @Masklinn That does answer the first part of my question on how mutable references are handled. But unfortunately it doesn't seem to provide a way to extend this to other types. As soon as I'm able to tomorrow, I'll try to play around with lifetimes as described in that post to see if I can come up with a solution, as the "hack" that I found seems to be related to these lifetimes as well, thank you. – Filipe Rodrigues Jun 11 '20 at 05:51
  • 1
    To fix your "hack", just remove the lifetime bound `'b: 'a` (this says `'b` is longer than `'a`, which, with the other constraints on `'b`, forces it to be equal to `'a`). To be usable, you want `'b` to be *shorter* than `'a`. – SCappella Jun 11 '20 at 05:56
  • 2
    Why don't just write `fn b2(_x: &mut I32RefMut)`? – rodrigo Jun 11 '20 at 07:13
  • Regarding "I wanted to restrict what methods could be called on the underlying type" - it might be too clever by half, but you can do this by coercing it to a `&mut dyn Trait` where `Trait` exposes only the behavior you want (and can be implemented for any `T: AsMut<[...]>` or whatever your real requirement is). Then you can take advantage of implicit reborrowing all you want. – trent Jun 11 '20 at 10:30
  • @rodrigo You're right, I believe I was thinking about this too much, I wanted a type with reference semantics, so I should have used a reference. – Filipe Rodrigues Jun 11 '20 at 16:35
  • @trentcl That's also a good solution, but unfortunately this is a part of the code that will be called in a tight loop so I'd prefer to avoid dynamic dispatch with vtables for the trait object, that way it's also possible for calls to be inlined using the wrapper. The solution proposed by rodrigo and Netwave works perfectly already though – Filipe Rodrigues Jun 11 '20 at 16:38
  • You could use `impl Trait` there instead to avoid dynamic dispatch, if monomorphization is not a problem for you. `dyn Trait` just came to mind first because of unrelated stuff I was working on recently. – trent Jun 11 '20 at 17:14

1 Answers1

2

The thing is that you are taking ownership of your new type instance when calling b. Just take a reference to your type instead to get access to the underlaying type:

pub fn a2(x: &I32RefMut) {
    for _i in 0..10 {
        b2(x);
    }
}

pub fn b2(_x: &I32RefMut) {

}

Playground

Actually if you want to mutate it you need to play around a bit with them:

pub fn a2(mut x: I32RefMut) {
    for _i in 0..10 {
        b2(&mut x);
    }
}

pub fn b2(_x: &mut I32RefMut) {
    *(_x.0) += 1
}

Playground

Netwave
  • 40,134
  • 6
  • 50
  • 93