Object with Drop impl and shared data


#1

Hey,

I want to add an auto object to my Modbus crate (IO protocol for industrial applications).

It should be possible to set a Coil (Bit) and get a ScopedCoil object back. If that object get’s out of scope, the Coil should be automatically toggled back by implementing Drop for ScopedCoil.

The Problem is, that all manipulation methods are in a Transport connection object. So it must be borrowed/referenced in the ScopedCoil object, to call the toggle method in drop().

pub enum CoilDropFunction {
    On,
    Off,
    Toggle,
}

pub struct ScopedCoil {
    pub address: u16,
    pub drop_value: Coil,
    pub transport: RefCell<Transport>,
}

impl Drop for ScopedCoil {
    fn drop(&mut self) {
        self.transport.borrow_mut().write_single_coil(self.address, self.drop_value).unwrap()
    }
}

impl ScopedCoil {
    fn new(transport: &mut Transport,
           address: u16,
           value: Coil,
           fun: CoilDropFunction)
           -> ModbusResult<ScopedCoil> {
        try!(transport.write_single_coil(address, value));
        let drop_value = match fun {
            CoilDropFunction::On => Coil::On,
            CoilDropFunction::Off => Coil::Off,
            CoilDropFunction::Toggle if value == Coil::On => Coil::Off,
            CoilDropFunction::Toggle if value == Coil::Off => Coil::On,
            _ => panic!("Impossible drop function"),
        };
        Ok(ScopedCoil {
            address: address,
            drop_value: drop_value,
            transport: RefCell::new(*transport), // <-- How can I store a ref to my transport
        })
    }

How can I store a reference (RefCell?) to my transport object?

Thanks, Falco


#2

not sure if a refcell is really needed here. wouldn’t a lifetime annotation suffice?

pub struct ScopedCoil<'a> {
    pub address: u16,
    pub drop_value: Coil,
    pub transport: &'a mut Transport,
}

#3

I don’t think so, because if I want to use it like this:

let mut trans = Transport::new("127.0.0.1").unwrap();
{
    let auto = ScopedCoil::new(&mut trans, 0, Coil::On, CoilDropFunction::Off).unwrap();
    assert_eq!(trans.read_coils(0, 1).unwrap(), vec![Coil::On]);
}
assert_eq!(trans.read_coils(0, 1).unwrap(), vec![Coil::Off]);

I get:

error: cannot borrow `trans` as mutable more than once at a time [E0499]
tests/lib.rs:154             assert_eq!(trans.read_coils(0, 1).unwrap(), vec![Coil::On]);

#4

You could access transport via the ScopedCoil - note the auto.mut_trans() below:

let mut trans = Transport::new("127.0.0.1").unwrap();
{
    let auto = ScopedCoil::new(&mut trans, 0, Coil::On, CoilDropFunction::Off).unwrap();
    assert_eq!(auto.mut_trans().read_coils(0, 1).unwrap(), vec![Coil::On]);
}
assert_eq!(trans.read_coils(0, 1).unwrap(), vec![Coil::Off]);

take rustys snippet and implement mut_trans() as follows - note that the lifetime 'a of the return value &'a mut Transport will be bound to the inner lifetime 'a of ScopedCoil<'a>

impl<'a> ScopedCoil<'a> {
    \\..
    pub fn mut_trans(&mut self) -> &'a mut Transport {
        self.transport
    }

Does this help?


#5

Unfortunately not:

tests/lib.rs:155:24: 155:28 error: `auto` does not live long enough
tests/lib.rs:155             assert_eq!(auto.mut_trans().read_coils(0, 1).unwrap(), vec![Coil::On]);

#6

Ok, I’ve got it, but I don’t really understand why it works:

mpl<'a> ScopedCoil<'a> {

...

    pub fn mut_trans<'b>(&'b mut self) -> &'b mut Transport {
        self.transport
    }

Why is there another lifetime parameter ('b) necessary?