Passing references

I'm trying to avoid just pushing everything into one module to avoid all the issues of moving stuff around which I seem to be hitting all the time. I have a module which handles a socket and I want to hand that socket out to other modules such as a reader, a writer etc. This is just see what happens code so please treat it as that.
So I want to pass in to my thread the receiver side of a channel (works) and a ref to the socket which gives me an error I don't understand -
error[E0759]: p_sock has an anonymous lifetime '_ but it needs to satisfy a 'static lifetime requirement.

Is there a right way to do this? Thanks for any help.

pub fn reader_start(receiver : crossbeam_channel::Receiver<i32>, p_sock : &socket2::Socket) {
    thread::spawn( move || {
        //reader_run(receiver, p_sock);
        reader_run(receiver);
    });
}

//pub fn reader_run(receiver : crossbeam_channel::Receiver<i32>, p_sock : &socket2::Socket) {
pub fn reader_run(receiver : crossbeam_channel::Receiver<i32>) {
    loop {
        thread::sleep(Duration::from_millis(100));
        // Check for termination code
        let r = receiver.try_recv();
        let res = match r {
            Ok(file) => break,
            Err(error) => continue,
        };

        // Perform read data
    }
}

The issue is that you can't put non-'static references into a thread::spawn(), since there is nothing to stop the thing the reference points at from going out of scope while the thread is running. The standard solution to this is to wrap it in an Arc to get shared ownership. socket2::Socket also offers try_clone() for a perhaps better-supported way of cloning a socket (although you would have to handle the error case).

There's now thread::scope().

3 Likes

Thanks. I understand the issue now but I struggle with syntax because I'm not sure what I'm doing yet. At the moment I can't get Arc to compile and thread::scope tells me its an unstable feature. I will keep trying but may eventually need help if I can't figure it.

It has been stabilized in rust 1.63.0, you're likely using an older version of the compiler.

2 Likes

There is, but that may not help in this case if creating the thread inside a function that receives a reference is desired.

The scope would have to be created in an outer function, with a reference to Scope passed in to the function creating the thread.

I'm not sure where to wrap in Arc. This is probably way wrong.
I have a sockets module that I want to return the socket ref from.

pub fn udp_sock_ref(&mut self) -> Arc<&socket2::Socket> {
            return Arc::new(&self.sock2);
        }

This is called from my UDP management module.

pub fn udp_init(&mut self) {
        let mut i_socket = udp_socket::Sockdata::new();
        i_socket.udp_socket_ann();
        i_socket.udp_revert_socket();
        let p_sock = i_socket.udp_sock_ref();

        udp_reader::reader_start(self.receiver.clone(), p_sock);
    }

In the reader module the reader thread is created.

pub fn reader_start(receiver : crossbeam_channel::Receiver<i32>, p_sock : Arc<&socket2::Socket>) {
    thread::spawn(  move || {
        reader_run(receiver, Arc::clone(p_sock));
    });
}

This throws an error:
error[E0308]: mismatched types
--> src\udp\udp_reader.rs:14:41
|
14 | reader_run(receiver, Arc::clone(p_sock));
| ^^^^^^
| |
| expected &Arc<&Socket>, found struct Arc
| help: consider borrowing here: &p_sock
|
= note: expected reference &Arc<&Socket>
found struct Arc<&Socket>
I don't understand what its trying to tell me here. Thanks again for any help.

The whole point of wrapping into an Arc is that you don't have a plain reference, but an owned value that can satisfy the 'static lifetime annotation. If you wrap the reference in an Arc, then it's basically useless. You have to wrap the owned value, like this.

1 Like

At the moment I'm going round in circles. I can't get all three modules to work together. First question on the top module, as if I need to wrap the value it needs to be done here.
Does this make sense, it compiles ok.

snippet from module udp_socket

pub struct Sockdata{
        sock2 : Arc<socket2::Socket>,
    }

impl Sockdata {
        pub fn new() -> Sockdata {
            let sock = Self::udp_open_bc_socket();
            Sockdata {  
                sock2 : Arc::new(socket2::Socket::from (sock)),
            }
        }     

        pub fn udp_sock_ref(&mut self) -> &socket2::Socket {
            return &self.sock2;
        }
}

It doesn't contain any errors that would be obvious to me. The &mut self is unnecessary, though.

Methods or functions (another area that confused me so I will leave the &mut self for another question).
So to the next module that calls udp_sock_ref().
snippet from udp module

pub fn udp_init(&mut self) {
        let mut i_socket = udp_socket::Sockdata::new();
        i_socket.udp_socket_ann();
        i_socket.udp_revert_socket();
        let p_sock = i_socket.udp_sock_ref();

        udp_reader::reader_start(self.receiver.clone(), p_sock);
    }

So nothing clever here just passing reference straight through to reader_start(). However this gives an error -
error[E0308]: mismatched types
--> src\udp\mod.rs:37:57
|
37 | udp_reader::reader_start(self.receiver.clone(), p_sock);
| ^^^^^^ expected struct Arc, found &Socket
|
= note: expected struct Arc<Socket>
found reference &Socket

I changed udp_reader module to your code which does not show any errors but there is a mismatch in the call to reader_start().

pub fn reader_start(receiver : crossbeam_channel::Receiver<i32>, p_sock : Arc<socket2::Socket>) {
    thread::spawn(  move || {
        reader_run(receiver, &p_sock);
    });
}

pub fn reader_run(receiver : crossbeam_channel::Receiver<i32>, p_sock : &socket2::Socket) {
    loop {
        thread::sleep(Duration::from_millis(100));
        // Check for termination code
        let r = receiver.try_recv();
        let res = match r {
            Ok(file) => break,
            Err(error) => continue,
        };

        // Perform read data
    }
}

Well that's trivial to fix. If you need to pass an Arc<T> and you have a T, then you convert it via Arc::new(). So you would pass Arc::new(p_sock) instead of just p_sock. But in this case, actually, it'd be better to leave the Arc as an implementation detail, so you would do this instead:

pub fn reader_start(receiver: crossbeam_channel::Receiver<i32>, p_sock: socket2::Socket) {
    let arc = Arc::new(p_sock);
    thread::spawn(move || {
        reader_run(receiver, &*arc);
    });
}

pub fn reader_run(receiver: crossbeam_channel::Receiver<i32>, p_sock: &socket2::Socket) {
    loop {
        thread::sleep(Duration::from_millis(100));
        // Check for termination code
        let r = receiver.try_recv();
        let res = match r {
            Ok(file) => break,
            Err(error) => continue,
        };

        // Perform read data
    }
}

Actually, at this point, you don't even need the Arc if you are going to take ownership of the socket anyway:

pub fn reader_start(receiver: crossbeam_channel::Receiver<i32>, p_sock: socket2::Socket) {
    thread::spawn(move || {
        reader_run(receiver, &p_sock);
    });
}

pub fn reader_run(receiver: crossbeam_channel::Receiver<i32>, p_sock: &socket2::Socket) {
    loop {
        thread::sleep(Duration::from_millis(100));
        // Check for termination code
        let r = receiver.try_recv();
        let res = match r {
            Ok(file) => break,
            Err(error) => continue,
        };

        // Perform read data
    }
}

But I'm not passing in a socket2::Socket I'm passing a &socket2::Socket. The whole point of this is that the udp_socket module manages the socket and is called to get a pointer to the socket. The socket is shared between several modules. I do appreciate the help but would it be possible to give code snippets for all three modules that will provide a path through or tell me if what I'm doing just isn't possible.

But that's not the function I'm talking about. You are passing the socket by-value to reader_start(). (Which in turn contains a move closure, which also takes ownership of the value.)

Unfortunately I don't know your exact requirements, so I can't just write all of the code, but if you do need the shared ownership (which isn't apparent from your code), then the original Arc-based solution should work.

I have posted snippets of the path so there is enough info there to work out a solution. It looks like this Arc thing can work for one level but gets overly complex or even impossible across several levels. I can restructure my code to have less levels but that would be disappointing to have to change the structure and this is only a tiny fragment of the large program I was planning to port.

Eventually got it to run but I'm still not sure exactly what Arc does. From sock_ref() I pass back a Arc::clone (is this a pointer?). Then pass that straight through the manager. Now in udp_reader I take whatever this Arcsocket2::Socket object is and new() it (why do I need this and why isn't it a clone()). Then pass it to the thread with &* which I've never seen used together so not sure what this does either. Sorry to have so many questions but I need to understand this before I can move on

udp_socket module

pub struct Sockdata{
        sock2 : Arc<socket2::Socket>,
    }

impl Sockdata {
        pub fn new() -> Sockdata {
            let sock = Self::udp_open_bc_socket();
            Sockdata {  
                sock2 : Arc::new(socket2::Socket::from (sock)),
            }
        }
       
        pub fn udp_sock_ref(&mut self) -> Arc<socket2::Socket> {
            return self.sock2.clone();
        }

udp manager module

pub fn udp_init(&mut self) {
        let mut i_socket = udp_socket::Sockdata::new();
        i_socket.udp_socket_ann();
        i_socket.udp_revert_socket();
        let p_sock = i_socket.udp_sock_ref();

        udp_reader::reader_start(self.receiver.clone(), p_sock);
    }

udp_reader module

pub fn reader_start(receiver : crossbeam_channel::Receiver<i32>, p_sock : Arc<socket2::Socket>) {
    let arc = Arc::new(p_sock);
    thread::spawn(  move || {
        reader_run(receiver, &*arc);
    });
}

pub fn reader_run(receiver : crossbeam_channel::Receiver<i32>, p_sock : &socket2::Socket) {
    loop {
        thread::sleep(Duration::from_millis(100));
        // Check for termination code
        let r = receiver.try_recv();
        let res = match r {
            Ok(file) => break,
            Err(error) => continue,
        };

        // Perform read data
    }
}

Yes, Arc is a pointer. It's an (a)tomic (r)eference-(c)ounted smart pointer. Shared ownership wouldn't be possible without indirection, since you have to heap-allocate a value in order to keep it alive from multiple independent places.

in fact it should just be a clone. If you Arc::new(another_arc), then you will have an Arc<Arc<_>>, which is useless.

There is nothing special with them being "together". Both just have their regular meaning, this is two prefix operators applied in sequence, a dereference (*) followed by a reference (&). I.e., it first dereferences the Arc, then takes a reference to its inner value, i.e. it converts it to a regular reference.

1 Like

Thanks. I was pretty sure that was how it was but needed to check. I've extended it now to my writer thread and there are a lot of clones. One to move a pointer out of udp_socket module. Two in the udp manager passing the instances to reader and writer and then another in the reader to pass to the thread and same on the writer. That's 5 clones which all seem to be necessary. I guess I will find out if it's sound when I start to use the socket.

The only thing I'm not quite sure about is that in the manager I just pass the clone but when I start the thread I clone it but then do the &* which passes the content so the thread knows nothing about Arc so how is the count decremented when the thread exits?

You pass in a move || … closure to thread::spawn(), so it captures the Arc by-value. (That was in fact the whole point of creating an Arc, because in this manner, the thread – having captured an Arc instead of a reference – can have the required 'static bound.)


The one inside reader_start() is not necessary. You already have the Arc parameter by-value. By analogy, it's probably not needed in the writer, either.

You are right, they appear not to be necessary. Time I think to turn this from test code to real code. There is a lot of protocol processing, UI and FFI stuff to do so by the end I should have explored a fair bit. Nice to know when I get stuck help is there. Thanks for sticking with this one.