Home > Mobile >  TcpStream::connect - match arms have incompatible type
TcpStream::connect - match arms have incompatible type

Time:02-06

I'm trying to write basic networking code in Rust, but running into an error I don't understand. I have been using match statements to error check everything in Rust so far, but when I try to error check TcpStream::connect(), I get an unexpected error:

My code:

use std::net::TcpStream;

fn main() {
    let mut server = match TcpStream::connect("127.0.0.1:23456"){
        Ok(x) => x,
        Err(x) => println!("Could not connect to server: {x}"),
    };
}

The compiler error:

error[E0308]: `match` arms have incompatible types
 --> src/main.rs:8:19
  |
6 |       let mut server = match TcpStream::connect("127.0.0.1:23456"){
  |  ______________________-
7 | |         Ok(x) => x,
  | |                  - this is found to be of type `TcpStream`
8 | |         Err(x) => println!("Could not connect to server: {x}"),
  | |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
                        expected struct `TcpStream`, found `()`
9 | |     };
  | |_____- `match` arms have incompatible types
  |

Every other time I use a match statement it allows me to destructure the Result type into a return value in the OK case (as above), or an error string in the error case.

It is the case that TcpStream::connect() returns a TcpStream, but why is the compiler insisting that the error case also needs to return a TcpStream?

CodePudding user response:

The value of the match statement gets assigned to server.

However, both branches of your match statement return a different type.

  • Ok(x) returns x, which is of type TcpStream.
  • Err(x) returns the result of println!(), which has the return value ().

TcpStream and () are incompatible.

Just think about the code after the match statement. What should the server variable be? You don't stop the execution when the error happens, you simply println!() and continue. So something has to be written to the server variable.

If you panic!() instead of println!(), meaning print and abort, then it compiles because it knows that the Err case won't continue afterwards:

use std::net::TcpStream;

fn main() {
    let mut server = match TcpStream::connect("127.0.0.1:23456") {
        Ok(x) => x,
        Err(x) => panic!("Could not connect to server: {x}"),
    };
}
thread 'main' panicked at 'Could not connect to server: Connection refused (os error 111)', src/main.rs:6:19

That said, if this is your desired behavior, there is a short form for it:

use std::net::TcpStream;

fn main() {
    let mut server = TcpStream::connect("127.0.0.1:23456").expect("Could not connect to server");
}
thread 'main' panicked at 'Could not connect to server: Os { code: 111, kind: ConnectionRefused, message: "Connection refused" }', src/main.rs:4:60

I recommend handling the error properly, though.

There are many ways to do that, so this part will be opinionated.

I personally like miette (an alternative would be anyhow):

use miette::{Context, IntoDiagnostic};
use std::net::TcpStream;

fn main() -> miette::Result<()> {
    let mut _server = TcpStream::connect("127.0.0.1:23456")
        .into_diagnostic()
        .wrap_err("Could not connect to server.")?;

    // Do something with server

    Ok(())
}
Error: 
  × Could not connect to server.
  ╰─▶ Connection refused (os error 111)

CodePudding user response:

Your server variable can't be both TcpStream and the unit type ()

Try propagating the error instead.

Here is an example from the official documentation:

use std::io::prelude::*;
use std::net::TcpStream;

fn main() -> std::io::Result<()> {
    let mut stream = TcpStream::connect("127.0.0.1:34254")?;

    stream.write(&[1])?;
    stream.read(&mut [0; 128])?;
    Ok(())
}

If it can't connect to the server, it emits an error message

Error: Os { code: 111, kind: ConnectionRefused, message: "Connection refused" }

And sets a non-zero exit code

$ echo $?
1
  • Related