How can I make the Ok() part of a Result ... print only its inner value?

let listener = match TcpListener::bind("127.0.0.1:7878") {
        Ok(v) => {
            println!("Sever started on {:?}", v.local_addr()); // this prints: "Server started on Ok(127.0.0.1:7878)"
            v
        }
        Err(e) => {
            println!("Error: {}", e.to_string());
            return;
        }
    };

How can I make that Ok() share the value from within? I tried: v.local_addr().into_ok(), but it yells at me:

the trait bound `!: From<std::io::Error>` is not satisfied
required for `std::io::Error` to implement `Into<!>`

What do you want to do when local_addr() is an Err? Rust is forcing you to think about that possibility.

The simplest thing to do is use the ? operator to return errors to the caller so you only have to deal with the green path. To do this make sure your function returns a Result<_, std::io::Error>, then you can write:

let listener = TcpListener::bind("127.0.0.1:78878")?;
println!("Server started on {:?}", v.local_addr()?);
5 Likes

Result::into_ok is for use with an uninhabited error. Getting the TcpListener::local_addr can fail with an io::Error, so you need to handle that in some way.

Either you can match the result from .local_addr() to handle Ok and Err separately, or if you're confident that the local addr will always be available, you can just .unwrap() the result.

2 Likes

The problem is that local_addr() may not return an Ok, and you have to handle that case.

One way is to call unwrap, i.e. v.local_addr().unwrap()--but that will cause your program to panic if the result is an Err.

Another option is to add a ?, i.e. v.local_addr()?--but that is only allowed if you are in a function that returns a Result.

A third option is to put in actual logic for the Err case. This could be another match:

match v.local_addr() {
  Ok(addr) => println!("Server started on {:?}", addr),
  Err(_) => (),
}

of an if let:

if let Ok(addr) = v.local_addr() {
   println("Server started on {:?}", addr);
}

or maybe specifying a default

let addr = v.local_addr().unwrap_or_else(|| SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); // Purely hypothetical

(I'm not sure why local_addr might fail but it seems like it can)

3 Likes

I'm sure there is a very good reason why the language was designed this way, but in my mind, I was hoping for a simple (intrinsic) way of having the Result enum itself... both, stringify and expose (return) the value from Ok() that can be easily printed out, in one single method (ie: .get_ok().to_string()).

Another thing I seem to be struggling with is this:

let listener = match TcpListener::bind("127.0.0.1:7878") {
        Ok(v) => {
            // once the "control flow" made it past this point, it is pretty much guaranteed that we are only dealing
            // with the Ok() part, so "v.local_addr()" should probably shave off the Ok() from the println!(), right?
            // so I should be able to access v.local_addr().to_string() from the Result enum, and perhaps only
            // use the "{}" instead of the "{:?}" - but clearly, I'm missing something!
            println!("Sever started on {:?}", v.local_addr()); // this prints: "Server started on Ok(127.0.0.1:7878)"
            v
        }
        Err(e) => {
            println!("Error: {}", e.to_string());
            return;
        }
    };

Using a match statement to access the value inside an enum is the simple intrinsic way to get at the value.

You can also use unwrap() if you want to handle the error by just crashing.

It sounds like you are assuming that just because we've bound to a port and have a TCP listener, we will always be able to access the local IP address.

However, this isn't always the case. To find the local address, we need to ask the OS what IP address and port is associated with a listener. This operation may fail, so local_addr() needs to return a Result<SocketAddr> so you can handle that error. The Result doesn't come from TcpListener::bind(), but from querying the OS.

From the getsockname(2) man page:

ERRORS     
       EBADF  The argument sockfd is not a valid file descriptor.

       EFAULT The addr argument points to memory not in a valid part of
              the process address space.

       EINVAL addrlen is invalid (e.g., is negative).

       ENOBUFS
              Insufficient resources were available in the system to
              perform the operation.

       ENOTSOCK
              The file desriptor sockfd does not refer to a socket.
5 Likes

Thank you for clarifying. Just out of curiosity: Would it be possible/viable to pass/redirect an error down into the same Ok() <--> Err(e) checking with the ? operator, such that the user would be able to catch the very first error in the error-stack-trace hierarchy, without the need for nesting multiple Results?

If it were me, I'd just write something like this:

fn listen_and_serve(bind_addr: &str) -> Result<()> {
  let listener = TcpListener::bind(bind_addr)?;

  let local_addr = listener.local_addr()?;
  println!("Listening on {local_addr}");

  for stream in listener.incoming() {
    // do something with the TCP connection
  }

  Ok(())
}

The key part is returning a Result so you can use ? to bail out if reading the local address fails. Looking at the types of errors you get on Linux (from that man page), I'd expect local_addr() errors to be very rare and indicate something is seriously wrong with the world, so returning from listen_and_serve() with an error is probably the right approach.

4 Likes

There is so much good information in all the replies. I really wish I could mark multiple answers as being part of the Solution ... ! :slight_smile: Thank you all.

2 Likes