Mutating struct fields using functions without mutable references


#1

Hi, I couldn’t think of a better title, but hopefully the explanation and the code will be clear.

I’m using gjio and gj for async IO. There’s a struct Y and it is a field used in many other structs such as struct X. There is a function that manipulates struct Y - fy. I can’t really use mutable references in fy because everything in the closure needs to be 'static. But if I do something like let p1 = fy(x.y);, then I can’t access x anymore because it’s moved. How do I structure my program so that I can get ownership back for the top level structs (struct X) after manipulating it with something like fy? (Also, I don’t want to clone the structs)

Thanks!

extern crate gj;
extern crate gjio;
use gj::{EventLoop, Promise};
use gjio::{AsyncRead, AsyncWrite, SocketStream};
use std::io::Error;

struct X {
    y: Y,
}

struct Y {
    y1: SocketStream,
    y2: SocketStream,
}

fn fx(mut x: X) -> Promise<X, Error> {
    x.y.y1.write(b"hello world").then(move |_| {
        x.y.y2.read(vec![0; 11], 11).map(move |(buf, _)| {
            assert_eq!(buf, b"hello world");
            Ok(x)
        })
    })
}

fn fy(mut y: Y) -> Promise<Y, Error> {
    y.y1.write(b"hello world").then(move |_| {
        y.y2.read(vec![0; 11], 11).map(move |(buf, _)| {
            assert_eq!(buf, b"hello world");
            Ok(y)
        })
    })
}

fn main() {
    EventLoop::top_level(|wait_scope| -> Result<(), Error> {
        let mut event_port = try!(gjio::EventPort::new());
        let network = event_port.get_network();

        let (y1, y2) = try!(network.new_socket_pair());
        let x = X { y: Y { y1: y1, y2: y2 } };

        let p1 = fy(x.y);
        // let p2 = fx(x); // doesn't work because x is moved

        let all = Promise::all(vec![p1].into_iter());
        try!(all.wait(wait_scope, &mut event_port));
        Ok(())
    }).expect("top level");

    println!("done!");
}


#2

The main structural problem here is that it’s not done with y after fy returns, because it eventually is going to return Ok(y) from the Promise. Without cloning, y can’t be both returned now and promised later.

If you changed this to promise (), or something else that can be owned, it’s possible. You do have to clone y2 so it can be owned by that closure too, but that’s just an Rc bump. This works:

extern crate gj;
extern crate gjio;
use gj::{EventLoop, Promise};
use gjio::{AsyncRead, AsyncWrite, SocketStream};
use std::io::Error;

struct X {
    y: Y,
}

struct Y {
    y1: SocketStream,
    y2: SocketStream,
}

fn fx(x: &mut X) -> Promise<(), Error> {
    let mut y2 = x.y.y2.clone();
    x.y.y1.write(b"hello world").then(move |_| {
        y2.read(vec![0; 11], 11).map(move |(buf, _)| {
            assert_eq!(buf, b"hello world");
            Ok(())
        })
    })
}

fn fy(y: &mut Y) -> Promise<(), Error> {
    let mut y2 = y.y2.clone();
    y.y1.write(b"hello world").then(move |_| {
        y2.read(vec![0; 11], 11).map(move |(buf, _)| {
            assert_eq!(buf, b"hello world");
            Ok(())
        })
    })
}

fn main() {
    EventLoop::top_level(|wait_scope| -> Result<(), Error> {
        let mut event_port = try!(gjio::EventPort::new());
        let network = event_port.get_network();

        let (y1, y2) = try!(network.new_socket_pair());
        let mut x = X { y: Y { y1: y1, y2: y2 } };

        let p1 = fy(&mut x.y);
        let p2 = fx(&mut x);

        let all = Promise::all(vec![p1, p2].into_iter());
        try!(all.wait(wait_scope, &mut event_port));
        Ok(())
    }).expect("top level");

    println!("done!");
}

#3

Thanks! I was under the impression that cloning creates another copy of the data rather than bumping the reference count, is this not correct?

I have seen Rc but never really used it. So in the case of fy, the reference to y.y2 is incremented and then decremented after the promise is evaluated, is that right?


#4

If you call clone() on an Rc itself, you bump the count. You can call borrow().clone() to get a new copy of the actual data. In this case, you are cloning a SocketStream value, but that’s just an Rc inside, so the interesting bits are reference counted.

Sort of, but not exactly. It’s not referencing y.y2. The Rc in y.y2 is a counted pointer on the heap. When it gets cloned, the new thing points to the same place on the heap, incrementing the count. Neither exclusively owns it – they share it. When any owner drops, it decrements the count, and when the count reaches 0 it’s freed from the heap.

So yes, that clone will decrement the count when it’s done. So will y.y2 whenever it drops. But generally speaking this could happen in either order. Neither is at all aware of the other except that the count can tell them they’re not alone. (e.g. Rc::is_unique())


#5

Oh yes, it’s indeed an Rc inside.

I think that’s what I meant, but I used the wrong terminology. Thanks again!