Again. Calling the self method inside a child thread created in the method

Hello. I recently started learning the Rust language. I am aware that I started learning how to create and use threads too early, but this got me interested. In small steps, I started writing a server program that will perform some actions (opening a command line, running a program, taking a screenshot) on the client.

Now I am writing a Client structure that describes the client and its implementation. I decided that this structure should contain at least stream: TcpStream to communicate with the client, receiving and sending data packets and thread_handle: Option<JoinHandle<()>> to have access to the stream (join, is_finished) , which should be engaged in receiving and transmitting packets.

In general, my program can be reduced to the following code:

use std::{net::{TcpStream, TcpListener}, thread::{JoinHandle, spawn}};

pub struct Client {
    stream: TcpStream,
    thread_handle: Option<JoinHandle<()>> // assign proper Some(JoinHandle) later
}
impl Client {
    pub fn new(stream: TcpStream) -> Client {
        Client {stream, thread_handle: None}
    }
    
    pub fn start_handler(&mut self) {
        self.thread_handle = Some(spawn(|| {/* need to call self.client_handler() */}))
    }

    fn client_handler(&mut self) {
        println!("Start processing packets with TcpStream...");
    }
}

fn main() {    
    for stream in TcpListener::bind("0.0.0.0:30442").unwrap().incoming() {
        match stream {
            Ok(stream) => {
                let mut client = Client::new(stream);
                client.start_handler(); 
            }, _ => () 
        }
    }
}

I decided that all packet handling would happen in the private client_handler() method, which should be called from the child thread created in the public start_handler() method.

But Rust won't let me do that because it violates the borrowing rules, since a thread can live longer than a struct object, right? Please advise in what ways I can bypass the system, if possible, or suggest a more convenient alternative.

This tries to create a self-referential struct (the thread handle runs a function that points to self, and you are trying to store the thread handle in self simultaneously). So not only is the lifetime wrong, this also tries to perform mutable aliasing. I don't think this is a feasible or good design.

It is weird that you should require storing the thread handle in the same type that the thread operates on. Why are you storing this in the Client? It seems like you shouldn't. However, if you really want to stick with this design for some reason, you can just put the client behind an Arc<Mutex<_>> or something:

impl Client {
    pub fn new(stream: TcpStream) -> Arc<Mutex<Self>> {
        Arc::new(Mutex::new(Client { stream, thread_handle: None }))
    }
    
    pub fn start_handler(this: Arc<Mutex<Self>>) {
        let twin = this.clone();
        let mut guard = this.lock().unwrap();
        guard.thread_handle = Some(spawn(move || {
            Client::client_handler(twin);
        }))
    }

    fn client_handler(this: Arc<Mutex<Self>>) {
        println!("Start processing packets with TcpStream...");
    }
}

So I figured out how to do it a bit thanks to @H2CO3. He also hinted that this implementation of Client has some weird logic, so I wrote another ClientHandle structure that will contain Client and Option<JoinHandle<()>>. Now the thread will be managed through ClientHandle, and the client will be managed through its client: Client field.

use std::{net::{TcpStream, TcpListener}, thread::{JoinHandle, spawn}, sync::{Arc, Mutex}, io::Error};

pub struct ClientHandle {
    client: Client,
    thread_handle: Option<JoinHandle<()>>
}
impl ClientHandle {
    pub fn new(client: Client) -> Arc<Mutex<Self>> {
        Arc::new(Mutex::new(
            ClientHandle {
                client, 
                thread_handle: None
            }
        ))
    }

    pub fn start_handler(this: Arc<Mutex<Self>>) {
        let this_cloned = this.clone();
        this
            .lock().unwrap()
            .thread_handle = Some(spawn(move || 
                this_cloned
                    .lock().unwrap()
                    .client
                    .client_handler()
            ));
    }
}

pub struct Client {
    stream: TcpStream,
}
impl Client {
    pub fn new(stream: TcpStream) -> Client {
        Client {
            stream
        }
    }

    pub fn peer_addr_string(&self) -> Result<String, Error> {
        match self.stream.peer_addr() {
            Ok(saddr) => 
                return Ok(format!("{}:{}", saddr.ip(), saddr.port())),
            Err(e) => 
                return Err(e)
        }
    }

    fn client_handler(&self) {
        println!("Start processing packets with TcpStream...");
        match self.peer_addr_string() {
            Ok(s) =>
                println!("New connection from {s}"),
            Err(e) => 
                println!("Failed to get peer address ({e})")
        }
    }
}

fn main() {    
    for stream in TcpListener::bind("0.0.0.0:30442").unwrap().incoming() {
        match stream {
            Ok(stream) => {
                let client_handle = ClientHandle::new(Client::new(stream));
                ClientHandle::start_handler(client_handle);
            }, _ => () 
        }
    }
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.