Search code examples
rustreferencechannelownershiprust-result

How to take T out of Result<Vec<Data<&T>>>?


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

Solution

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