Doing operation on types where source and destination reference could be the same

Below I have a function that could work on the same data as source and destination, or different ones. If you pass no source, it should treat the destination as the source.

use core::marker::PhantomData;

struct B<T>{
    _phantom: PhantomData<T>
}

struct BMut<T>{
    _phantom: PhantomData<T>
}

impl<T> From<BMut<T>> for B<T> {
    fn from(_e: BMut<T>) -> Self {
        B{
            _phantom: PhantomData
        }
    }
}


trait A where Self: Sized {
    fn a(b: Option<&mut B<Self>>, b_mut: &mut BMut<Self>);
}

impl A for u64 {
    fn a(b: Option<&mut B<Self>>, b_mut: &mut BMut<Self>) {
        let b = match b{
            Some(b) => b,
            None => b_mut.into()
        };
    }
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c95e615c1303371ce1965fca3216e7e4

The problem is that I cannot convert BMut to B. Yes, I could access BMut in the function a everytime I needed B if the source was not specified, but would be much cooler to have one type for source and be able to set it as destination only once.

Standard Error

   Compiling playground v0.0.1 (/playground)
error[E0277]: the trait bound `&B<'_, u8>: From<&mut BMut<'a, u8>>` is not satisfied
  --> src/lib.rs:21:29
   |
21 |         None => destination.into()
   |                             ^^^^ the trait `From<&mut BMut<'a, u8>>` is not implemented for `&B<'_, u8>`
   |
note: required because of the requirements on the impl of `Into<&B<'_, u8>>` for `&mut BMut<'a, u8>`
  --> src/lib.rs:11:13
   |
11 | impl<'a, T> Into<B<'a, T>> for BMut<'a, T> {
   |             ^^^^^^^^^^^^^^     ^^^^^^^^^^^

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` due to previous error

By the way, in a more realistic example, B implements Index and BMut implements IndexMut. What I want to do is be able to either apply the operation to BMut and modify it inplace (so source = destination, of modify from B to another BMut, so source not equals destination)

here's a more detailed self-explanatory example:


use core::ops::IndexMut;
use core::ops::Index;
use core::marker::PhantomData;

struct B<'a, T>{
    _phantom: PhantomData<&'a T>
}

struct BMut<'a, T>{
    _phantom: PhantomData<&'a mut T>
}

impl<'a, T> Index<T> for B<'a, T> {
    type Output = T;
    fn index(&self, t: T) -> &T {
        unimplemented!()
    }
}

impl<'a, T> Index<T> for BMut<'a, T> {
    type Output = T;
    fn index(&self, t: T) -> &T {
        unimplemented!()
    }
}

impl<'a, T> IndexMut<T> for BMut<'a, T> {
    fn index_mut(&mut self, t: T) -> &mut T {
        unimplemented!()
    }
}

impl<'a, T> From<BMut<'a, T>> for B<'a, T> {
    fn from(_e: BMut<'a, T>) -> Self {
        B{
            _phantom: PhantomData
        }
    }
}

trait A where Self: Sized {
    fn transform(source: Option<&B<Self>>, destination: &mut BMut<Self>);
}

impl A for u64 {
    fn transform(source: Option<&B<Self>>, destination: &mut BMut<Self>) {
        let source = match source{
            Some(source) => source,
            None => destination.into()
        };
        //do a transformation here that reads and writes from destination.
        //In case we specified a source, then it uses it, otherwise uses destination
        //as a source. Technically this should be possible because it's impossible to pass 
        //a source that is equal to destination
    }
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=15a2cae5c1240890e6f611d79e74958f

I have an indexable object and a mutable indexable one. I want to be able to either transform the mutable one, or transform from the non-mutable one to the mutable one.

Generalizing over mut and non-mut is often a difficult or impossible situation. The English description of what you're trying to accomplish sounds like one such case.

However, if I just try to modify your code, here's where I arrived. From is about transforming owned values, not borrowed ones (including borrowed reference-holding values), so I didn't use that.

impl A for u64 {
    fn transform<'b, 'a>(source: Option<&'b B<'a, Self>>, destination: &'b mut BMut<'_, Self>) {
        let source = source.map(|b| b.corporeal).unwrap_or(&*destination.corporeal);
    }
}

Note 1:

You can get a &'a T out from source because shared references implement Copy. But it is unsound to get the longer lifetime ('a) from underneath the shorter lifetime ('b) in the case of nested exclusive references (&mut). So be aware that the local source variable here is limited to the shorter 'b lifetime.

Note 2:

Because the BMut borrow is exclusive, and because the local source variable may be a reborrow of that, destination will be inaccessible for the lifetime of (the local) source. You'll have to build the transformation and then write it to the destination, vs. writing the transformation directly into destination.

Because the BMut borrow is exclusive, and because the local source variable may be a reborrow of that, destination will be inaccessible for the lifetime of (the local) source . You'll have to build the transformation and then write it to the destination, vs. writing the transformation directly into destination .

unfortunately this is what I was trying to avoid. I wanted to do the transformation without reallocating or copying to a pre-allocated place. I guess there's no way then?

There's no safe way to have both a shared reference (&) and exclusive reference (&mut) to the same memory usable at the same time (e.g. interleaving reads and writes). That's a fundamental property of safe Rust. In the given example, every use of destination will invalidate the local source variable.

But it's still not impossible. You can

  • Recalculate source after every use of destination
    • It might get optimized away (but I'd check to be sure)
  • Handle the modify-in-place case separately, reading and writing through a single &mut
    • Probably the clearest and most common approach
  • Use some sort of interior mutability
  • Do something unsafe instead
    • I don't recommend this approach
1 Like