How to handle error in this case

I am trying to read data from the a device on one TCP port, format it, and then send the new data to a printer on another TCP port. I have lifted the sample code from the tokio docs. The TCP listener works as expected but I cannot get the peer connection working. If I use the samples individually in a main() function they work correctly. But when I combine them I am getting a method not found error with the write_all function in start_pos_listener(). I see that it is coming back with a Result but I have not been able to resolve it with a match statement. I understand that printer_socker.write_all is being called from tokio::spawn() which has no return type so I cannot pass the error up with ?. Any help would be appreciated. Thanks and Happy New Year to all.

main.rs

use std::error::Error;
use label_printer::Config;
use std::env;
use std::process;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let args: Vec<String> = env::args().collect();

    let config = Config::new(&args).unwrap_or_else(|err| { 
        println!("Problem parsing arguments: {}", err);
        process::exit(1);
    });
    match label_printer::start_pos_listener(config.pos, config.printer).await {
        Ok(x) => x,
        Err(_) => println!("Listener failure"),
    };
    Ok(())

lib.rs

use tokio::net::TcpListener;

use tokio::io::{AsyncReadExt, AsyncWriteExt};

use tokio::net::TcpSocket;

use std::error::Error;

//use std::io;

pub struct Config {

    pub pos: String,

    pub printer: String,

}

impl Config {

    pub fn new(args: &[String]) -> Result<Config, &'static str> {

        if args.len() == 1 {      //TODO: replace defaults with registry entries

            let pos_ip = "192.168.0.49".to_string();

            let pos_port = "9101".to_string();

            let printer_ip = "192.168.0.62".to_string();

            let printer_port = "9100".to_string();

            let mut pos = String::new();

            pos = pos + &pos_ip.trim() + ":" + &pos_port.trim();

            let mut printer = String::new();

            printer = printer + &printer_ip.trim() + ":" + &printer_port.trim();

            Ok(Config {pos, printer})

        } else {

            if args.len() >= 4 {

                let pos_ip = args[1].clone();

                let pos_port = args[2].clone();

                let printer_ip = args[3].clone();

                let printer_port = args[4].clone();

                let mut pos = String::new();

                pos = pos + &pos_ip.trim() + ":" + &pos_port.trim();

                let mut printer = String::new();

                printer = printer + &printer_ip.trim() + ":" + &printer_port.trim();

                Ok(Config {pos, printer})

            } else {

                Err("not enough arguments")

            }

        }

    }

}

pub async fn start_pos_listener(pos: String, printer: String) -> Result<(), Box<dyn Error>> {

    let listener = TcpListener::bind(pos).await?;

    loop {

        let (mut socket, _) = listener.accept().await?;

        tokio::spawn(async move {

            let mut buf = [0; 8192];

            // In a loop, read data from the socket and write the data back.

            loop {

                let n = match socket.read(&mut buf).await {

                    // socket closed

                    Ok(n) if n == 0 => return,

                    Ok(n) => n,

                    Err(e) => {

                        println!("failed to read from socket; err = {:?}", e);

                        return;

                    }

                };

                let printer = "192.168.0.62:9100";

                let addr = printer.parse().unwrap();

                println!("{}", addr);

                let printer_socket = TcpSocket::new_v4();

                // Write the data back

                let e = match printer_socket.write_all(&buf[0..n]).unwrap().await {

                    Err(it) => it,

                    _ => continue,

                };

                println!("failed to write to socket; err = {:?}", e);

                return; 

            }

        });

    }

}

The error message is apparently

error[E0599]: no method named `write_all` found for enum `std::result::Result<TcpSocket, std::io::Error>` in the current scope
   --> src/main.rs:121:50
    |
121 |                       let e = match printer_socket.write_all(&buf[0..n]).unwrap().await {
    |                                                    ^^^^^^^^^ method not found in `std::result::Result<TcpSocket, std::io::Error>`
    |
    = note: the method `write_all` exists but the following trait bounds were not satisfied:
            `std::result::Result<TcpSocket, std::io::Error>: AsyncWrite`
            which is required by `std::result::Result<TcpSocket, std::io::Error>: AsyncWriteExt`

So it says
Result<TcpSocket, ...>: AsyncWrite does not hold. Should get you thinking you might have forgotten a unwrap or ? for a Result somewhere. Looking more closely,

let printer_socket = TcpSocket::new_v4();

let e = match printer_socket.write_all(&buf[0..n]).[...]

but the docs say

pub fn new_v4() -> Result<TcpSocket>

any you should know where it is.

Edit: Wait.. nevermind, perhaps I should try my own advice and see if it works before posting it, hang on...

Okay, pardon me being not too much into networking stuff, but apparently you cannot write to a TcpSocket. And you are also not using the address addr either? Maybe just create a TcpStream instead... Without any idea if this does what you want, changing it to something like

                   let printer = "192.168.0.62:9100";

                    //let addr = printer.parse().unwrap();

                    //println!("{}", addr);

                    let mut printer_stream = TcpStream::connect(printer).await.unwrap();

                    // Write the data back

                    let e = match printer_stream.write_all(&buf[0..n]).await {
                        Err(it) => it,

                        _ => continue,
                    };

makes it compile at least. No good error handling of course with that unwrap ^^

Edit: I’m wondering why you don’t just write to your socket variable (which apparently contains a TcpStream, so the naming is a bit confusing). Or is the sending supposed to go to a different connection? Then re-connecting in the loop is still probably not a good idea.

                loop {
                    let n = match socket.read(&mut buf).await {
                        // socket closed
                        Ok(n) if n == 0 => return,

                        Ok(n) => n,

                        Err(e) => {
                            println!("failed to read from socket; err = {:?}", e);

                            return;
                        }
                    };

   

                    let e = match socket.write_all(&buf[0..n]).await {
                        Err(it) => it,

                        _ => continue,
                    };

                    println!("failed to write to socket; err = {:?}", e);

                    return;
                }

@steffahn Thanks for your efforts. This compiles. I will run with it from here. Have a great day/night.

What's up @steffahn? I have seen you replying a few times.

Okay, I guess you were also asking about error-handling :D. You identified correctly that you cannot get the error out of the tokio::spawn call. This is because if you don’t keep the handle returned by tokio::spawn then you don’t have an easy way to access the return value (including potential error return values) of the spawned task once it finishes. But I suppose it is not wrong not to keep the JoinHandles around because: What is the loop that accepts connections supposed to do with them otherwise?

I guess the strategy also depends on what you want to have happen on an error. One way to get error information back to some form of central place could be using channels. I don’t really know how best to structure applications with regards to error handling, so I don’t know if I should even give advice here.

One way I could imagine that could even detect panics in spawned tasks would be to keep the JoinHandles after all and send them (with a channel) to another task that collects them up in a FuturesUnordered, which could then be used to find out about and react to any panic or error that happend in any of the tasks. What it is going to do then? I don’t know. Perhaps some logging. Or shutting the entire application down on panic if that is what we want. Although that last thing could be more easily archieved by configuring Rust to abort on panics...

@steffahn A conundrum wrapped in an enigma...Now that I have the basics working (I can receive and send data) I will next ponder the question of what is/isn't an error in this context and how to deal with it. Thank you for time and consideration. It is greatly appreciated.