tokio-rs/tokio · error · io::Error

could not resolve to any address

Error message

could not resolve to any address

What it means

TcpListener::bind resolves the address via to_socket_addrs, iterates each candidate, and on the failure path calls unwrap_or_else to build this InvalidInput error only when last_err is None — i.e. the resolved iterator was empty. If at least one address was tried, the OS-level bind error is returned instead. So this message specifically means 'DNS returned zero usable addresses.'

Source

Thrown at tokio/src/net/tcp/listener.rs:115

        ///
        ///     # let _ = listener;
        ///     Ok(())
        /// }
        /// ```
        pub async fn bind<A: ToSocketAddrs>(addr: A) -> io::Result<TcpListener> {
            let addrs = to_socket_addrs(addr).await?;

            let mut last_err = None;

            for addr in addrs {
                match TcpListener::bind_addr(addr) {
                    Ok(listener) => return Ok(listener),
                    Err(e) => last_err = Some(e),
                }
            }

            Err(last_err.unwrap_or_else(|| {
                io::Error::new(
                    io::ErrorKind::InvalidInput,
                    "could not resolve to any address",
                )
            }))
        }

        fn bind_addr(addr: SocketAddr) -> io::Result<TcpListener> {
            let listener = mio::net::TcpListener::bind(addr)?;
            TcpListener::new(listener)
        }
    }

    /// Accepts a new incoming connection from this listener.
    ///
    /// This function will yield once a new TCP connection is established. When
    /// established, the corresponding [`TcpStream`] and the remote peer's
    /// address will be returned.
    ///

View on GitHub (pinned to 625954f365)

Solutions

  1. Pre-resolve with tokio::net::lookup_host and inspect the iterator length before binding.
  2. Use a literal IP (127.0.0.1:port or [::]:port) instead of a hostname for listener binds — the common idiom.
  3. Verify DNS records externally (dig/nslookup) for the hostname.
  4. Distinguish this case from a real bind failure by checking that error.kind() == InvalidInput AND no os error code is attached.

Example fix

// before
let l = TcpListener::bind("svc.example:8080").await?;

// after
let l = TcpListener::bind(("0.0.0.0", 8080)).await?;
// or pre-resolve:
let mut addrs = tokio::net::lookup_host("svc.example:8080").await?;
if addrs.next().is_none() {
    return Err(anyhow::anyhow!("no addresses resolved"));
}
Defensive patterns

Strategy: validation

Validate before calling

// Pre-resolve and require a non-empty address list:
let addrs: Vec<_> = tokio::net::lookup_host((host.as_str(), port)).await?.collect();
if addrs.is_empty() {
    return Err(anyhow::anyhow!("{host} resolved to no addresses"));
}
let listener = TcpListener::bind(addrs[0]).await?;

Type guard

fn is_empty_resolution(e: &io::Error) -> bool {
    e.kind() == io::ErrorKind::InvalidInput && e.raw_os_error().is_none()
}

Try / catch

match TcpListener::bind(addr).await {
    Ok(l) => Ok(l),
    Err(e) if e.kind() == io::ErrorKind::InvalidInput && e.raw_os_error().is_none() => {
        Err(anyhow::anyhow!("no addresses resolved for {addr}"))
    }
    Err(e) => Err(e.into()),
}

Prevention

When it happens

Trigger: Calling TcpListener::bind(addr) where addr is a hostname:port string whose resolution yields no A/AAAA records, or an empty address list. The for loop body never executes, last_err stays None, and the fallback error fires.

Common situations: Misspelled hostname; a service that has no DNS record (bare SRV/CNAME with no A); /etc/hosts misconfiguration; resolver returning an empty answer; passing an empty-string host that resolves to nothing on the platform.

Related errors


AI-assisted analysis of tokio-rs/tokio@625954f365 (2026-08-11). Data as JSON: /api/errors/0db36d41bddbe7a4. Report an issue: GitHub.