0

I'd like to take SomeType out of Result<Vec<Data<&SomeType>>>, and then pass it by a channel, but I failed:

pub fn read(result: Result<Vec<Data<&SomeType>>, ()>, tx: mpsc::Sender<SomeType>) {
    if let Ok(datas) = result {
        for data in datas.iter() {
            let actual_data = data.value();
            let some_type_instance = SomeType { k: 1 };
            tx.send(some_type_instance); // works
            tx.send(**actual_data); // errors
        }
    }
}

errors with:

error[E0507]: cannot move out of `**actual_data` which is behind a shared reference
  --> src/main.rs:40:21
   |
40 |             tx.send(**actual_data);
   |                     ^^^^^^^^^^^^^ move occurs because `**actual_data` has type `SomeType`, which does not implement the `Copy` trait

It seems that tx didn't take the ownership correctly. Although implementing the Copy trait on SomeType can eliminate the error, I am not sure if Copy or Clone would reduce the performance. I am struggling with it but couldn't find a correct way to fix it.

The following is a complete code to regenerate the error.

use std::result::Result;
use std::sync::mpsc;

pub struct SomeType {
    k: i32,
}

pub struct Data<D> {
    value: D,
}

impl<D> Data<D> {
    pub fn value(&self) -> &D {
        &self.value
    }
}

pub fn main() {
    let a = SomeType { k: 1 };
    let b = SomeType { k: 2 };
    let c = SomeType { k: 3 };

    let A = Data { value: &a };
    let B = Data { value: &b };
    let C = Data { value: &c };

    let datas = vec![A, B, C];

    let result = Ok(datas);
    let (tx, rx) = mpsc::channel();
    read(result, tx);
}

pub fn read(result: Result<Vec<Data<&SomeType>>, ()>, tx: mpsc::Sender<SomeType>) {
    if let Ok(datas) = result {
        for data in datas.iter() {
            let actual_data = data.value();
            let some_type_instance = SomeType { k: 1 };
            tx.send(some_type_instance); // this line works
            tx.send(**actual_data); // this line errors
        }
    }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
xc wang
  • 318
  • 1
  • 3
  • 14
  • What you tried can't work because you cannot have a `T` value in two places at the same time. In other words, you can't keep `T` in a `Data` _and_ return it from `Data::data()`. The correct solution will depend on what exactly you want to do. Without knowing the details, I can recommend holding `Arc` in `Data` and having `data()` return `Arc` implemented as `return Arc::clone(&self.value)`. You should be able to send the `Arc` through the channel. – user4815162342 Sep 19 '21 at 16:02
  • @user4815162342 Thank you. Is there a way only by changing the body of `fn read()`, since in my actual application, result is obtained by calling an imported external crate. If I cannot have a `T` value in two places at the same time, as you mentioned, is it possible to destroy the first place and let the second place take ownership? – xc wang Sep 20 '21 at 01:52
  • 1
    If you control the `T` with which you construct `Data`, then it is possible. One thing you have to make sure is that the value inside `Data` _remains valid_ after you extract `SomeType` out of it. A simple and effective way of achieving that is by using `Option`. An option allows you to take the value it holds and leave `None` in its place, leaving the option in a different but valid state. The option type even has a `take()` method which does just that, but requires a mutable reference. Which brings us to the other issue, that of `Data::read()` returning a shared reference... – user4815162342 Sep 20 '21 at 13:06
  • 1
    You can work around `Data::read()` returning `&T` by using interior mutability, i.e. `RefCell – user4815162342 Sep 20 '21 at 13:07
  • @user4815162342 Thanks for your help. It has increased my knowledge and really helps in understanding the mechanism. – xc wang Sep 22 '21 at 01:26
  • @user4815162342 Yes, please, that couldn't be any better! – xc wang Sep 23 '21 at 01:13

1 Answers1

1

When all you have is a &T, you cannot get a T without cloning the value behind the reference, because extracting a non-copy value would move it, and the owner of the T (here Data) who gave out the reference expects the value to remain valid.

However, if you control the type stored into Data, you can wrap your actual value in an Option. Then you can use std::mem::replace(ref_to_t, None) to obtain the value behind the reference and leave None in its place. Option even has a take() method that does that for you.

But both mem::replace() and Option::take() require a mutable reference, and all you have is a shared reference. To work around that, you need to also use interior mutability, such as provided by RefCell. The type you will then put in Data is RefCell<Option<SomeType>> - don't be put off by the nested generics, just read them as "RefCell containing an optional SomeType"). RefCell has a borrow_mut() method giving you a mutable reference to the inner content, on which you can then call Option::take(), or you can call RefCell::take() which will do the right thing by itself.

pub fn main() {
    let a = SomeType { k: 1 };
    let b = SomeType { k: 2 };
    let c = SomeType { k: 3 };

    let da = Data {
        value: RefCell::new(Some(a)),
    };
    let db = Data {
        value: RefCell::new(Some(b)),
    };
    let dc = Data {
        value: RefCell::new(Some(c)),
    };

    let datas = vec![da, db, dc];

    let (tx, _rx) = mpsc::channel();
    read(&datas, tx);
}

pub fn read(datas: &[Data<RefCell<Option<SomeType>>>], tx: mpsc::Sender<SomeType>) {
    for data in datas {
        let actual_data = data.value().take().unwrap();
        tx.send(actual_data).unwrap();
    }
}
user4815162342
  • 141,790
  • 18
  • 296
  • 355