Skip to content

getnameinfo and getaddrinfo, which say in their types what they return and why they failed #635

Open

Description

@kazu-yamamoto

Since #151, a failing socket-related call, such as bind, listen or accept, can be told apart by its error code: on POSIX it is in ioe_errno, and on Windows the Winsock code is now kept there too (#631), with the IOErrorType it stands for (#632). getNameInfo and getAddrInfo were left out. They report an EAI_* code rather than an errno, so ioe_errno is Nothing, the type is always NoSuchThing, and the reason survives only in the message.

getNameInfo and getAddrInfo each have two problems: the result type does not say what comes back, and a failure loses its reason.

getNameInfo

getNameInfo :: [NameInfoFlag] -> Bool -> Bool -> SockAddr
            -> IO (Maybe HostName, Maybe ServiceName)

getAddrInfo

getAddrInfo :: GetAddrInfo t
            => Maybe AddrInfo -> Maybe HostName -> Maybe ServiceName
            -> IO (t AddrInfo)   -- t is [] or NonEmpty
  • Result. The shape is chosen through a hidden class, kept for backward compatibility. With [], callers handle an empty list that cannot happen: getaddrinfo(3) never succeeds with no result.
  • Failure. The same as getNameInfo: an EAI_* code becomes an IOException with only a message.

In both, a caller cannot tell EAI_AGAIN (try again) from EAI_NONAME (there is no such name) without parsing the message.

Proposal

Add getnameinfo and getaddrinfo, sharing one error type, and leave getNameInfo and getAddrInfo as they are. They are named exactly after the C functions because they report failures as the C functions do, by an EAI_* code; the C naming also sets them apart from the camelCase ones at a glance.

-- | An @EAI_*@ code, as getaddrinfo(3) and getnameinfo(3) return it.
--   'EAI_SYSTEM' carries the errno it stands for.
data GaiError = GaiError CInt | EAI_SYSTEM Errno
-- POSIX
pattern EAI_AGAIN, EAI_BADFLAGS, EAI_FAIL, EAI_FAMILY, EAI_MEMORY,
        EAI_NONAME, EAI_OVERFLOW, EAI_SERVICE, EAI_SOCKTYPE :: GaiError
-- not POSIX, but returned by glibc, macOS and (EAI_NODATA) Windows
pattern EAI_NODATA, EAI_ADDRFAMILY :: GaiError
{-# COMPLETE GaiError, EAI_SYSTEM #-}
-- Eq, Show showing the pattern names, and Exception
-- (displayException = gaiErrorString), so `either throwIO return` works.

-- | What the code means, for people.
gaiErrorString :: GaiError -> String

-- | What to look up, and so what comes back.
data NameInfoQuery a where
    QueryHost           :: NameInfoQuery HostName
    QueryService        :: NameInfoQuery ServiceName
    QueryHostAndService :: NameInfoQuery (HostName, ServiceName)

getnameinfo :: [NameInfoFlag] -> NameInfoQuery a -> SockAddr
            -> IO (Either GaiError a)

getaddrinfo :: Maybe AddrInfo -> Maybe HostName -> Maybe ServiceName
            -> IO (Either GaiError (NonEmpty AddrInfo))

Use:

r <- getnameinfo [NI_NAMEREQD] QueryHost addr
case r of
    Right host              -> ...   -- host :: HostName, no Maybe
    Left EAI_NONAME         -> ...   -- no name for this address
    Left EAI_AGAIN          -> ...   -- try again later
    Left (EAI_SYSTEM errno) -> ...   -- the system call failed with errno
    Left e                  -> fail (gaiErrorString e)

r' <- getaddrinfo (Just hints) (Just host) (Just port)
case r' of
    Right (ai :| _) -> ...           -- the best address
    Left EAI_AGAIN  -> ...           -- retry
    Left e          -> fail (gaiErrorString e)

Error codes

The codes differ by OS, so the patterns take their values with #const, as network's other patterns do:

macOS glibc Windows
values positive (EAI_NONAME = 8) negative (EAI_NONAME = -2) Winsock codes (EAI_NONAME = WSAHOST_NOT_FOUND)
EAI_SYSTEM yes yes no
EAI_OVERFLOW yes yes no
EAI_NODATA, EAI_ADDRFAMILY yes with _GNU_SOURCE EAI_NODATA only
  • A code an OS lacks gets a value that is never returned there.
  • A code without a pattern is kept as GaiError n, not lost.
  • errno is read only for EAI_SYSTEM; after any other code its value is unspecified. Windows has no EAI_SYSTEM: the returned code is the Winsock error itself, as in ioe_errno.

gaiErrorString is pure: it looks a code up and nothing else.

  • POSIX: gai_strerror(3), which returns constant strings.
  • Windows: network's table of Winsock errors (getWSErrorDescr), since the codes are Winsock codes; gai_strerror there writes to a static buffer and is not thread-safe. Codes not in the table are formatted in Haskell rather than in that function's static buffer.
  • EAI_SYSTEM: the description of its errno, rather than gai_strerror's "System error".

Notes

  • Every failure getnameinfo(3) or getaddrinfo(3) reports, an unsupported address family (EAI_FAMILY) included, comes back as Left; nothing about resolving a name is thrown. The only exception left is the one every function taking a SockAddr has: a SockAddrUnix path too long to marshal.
  • NameInfoQuery is not in C: the arguments of getnameinfo differ from getnameinfo(3) there, and its documentation will say so. It needs GADTs, which all supported compilers have.
  • Flags which do not apply to a query, such as NI_NAMEREQD with QueryService, are allowed and documented rather than ruled out by the types.
  • All of these are exported from Network.Socket, next to getNameInfo and getAddrInfo.
  • The API stays compatible, so this does not have to wait for v4.0 (v4.0 #557).
  • getNameInfo and getAddrInfo will not be deprecated: they have far too many users.
  • A prototype of the types, including EAI_SYSTEM carrying its Errno, compiles cleanly with -Wall and is used as above.

Activity

  1. changed the title [-]A getNameinfo which says in its type what it returns and why it failed[/-] [+]getNameinfo and getAddrinfo, which say in their types what they return and why they failed[/+] on Nov 13, 2026
  2. changed the title [-]getNameinfo and getAddrinfo, which say in their types what they return and why they failed[/-] [+]getnameinfo and getaddrinfo, which say in their types what they return and why they failed[/+] on Nov 13, 2026
  3. kazu-yamamoto commented on Nov 13, 2026

    @kazu-yamamoto
    CollaboratorAuthor

    @vdukhovni Any opinions on this issue?

  4. peti commented on Nov 13, 2026

    @peti

    I am very happy with the direction of this proposal and believe it to be a clear improvement over the current state!

    My main review points are:

    1. The values of missing codes must not collide.

      "A code an OS lacks gets a value that is never returned there" needs a concrete rule. The convention used elsewhere in the package, (-1) (e.g. Options.hsc, Posix/Cmsg.hsc), does not work here:

      • On glibc and musl, -1 is EAI_BADFLAGS. All codes happen to exist there today because HsNet.h defines _GNU_SOURCE, but on any glibc-like platform lacking one of them, that pattern would silently match a different, real error.
      • On Windows, EAI_OVERFLOW and EAI_ADDRFAMILY are both missing. With a shared sentinel they would compare equal and Show could not name them correctly.

      Each missing code should get a distinct value outside the platform's real range, and the Eq/Show behaviour for that case should be stated.

    2. Two patterns may share a value on Windows

      This needs checking against the actual headers: I believe the Microsoft SDK has #define EAI_NODATA EAI_NONAME, while mingw-w64 (which GHC ships) uses WSANO_DATA. If the values coincide on any supported toolchain, Left EAI_NODATA would also catch EAI_NONAME, which arm wins would depend on arm order, and Show would print one name for both. The table under "Error codes" should give the actual values per toolchain.

    3. The platform table is incomplete

      It covers macOS, glibc and Windows only. network also builds on FreeBSD, OpenBSD, NetBSD, Solaris and musl, and the BSDs in particular have deprecated or removed EAI_NODATA and EAI_ADDRFAMILY.

    4. GaiError can represent the same error two ways

      With the raw constructors exported, GaiError (#const EAI_SYSTEM) (an EAI_SYSTEM without an errno) and EAI_SYSTEM e are two forms of the same error that derived Eq treats as different, and GaiError 0 is constructible but is not an error. Either document that only the library constructs values, or make the type abstract and export the patterns plus something like gaiErrorCode :: GaiError -> CInt.

      Two smaller notes on the type: I believe {-# COMPLETE GaiError, EAI_SYSTEM #-} has no effect, since those are real constructors and already complete; and Errno has no Show instance, so Show GaiError has to be hand-written.

    5. Naming

      This is the most debatable part. getAddrInfo and getaddrinfo differ only in case, which invites mistakes when typing, completing, grepping and reviewing. The justification, that they report failures as C does, is weak, because both functions already differ from C in their arguments and results (NameInfoQuery, Maybe hints, NonEmpty). Practical side effects:

      • Info.hsc already has local helpers named getaddrinfo and getnameinfo in where clauses, which would shadow the new exports and trigger -Wall warnings.
      • Downstream modules that FFI-import getaddrinfo under that name and import Network.Socket unqualified will get ambiguity errors. PVP permits this, but it is real churn.

      I'd suggest considering distinct names, or putting the new API in its own module (e.g. Network.Socket.Resolve) rather than exporting it from Network.Socket.

    Smaller points:

    • gaiErrorString is not entirely pure. glibc's gai_strerror returns translated messages depending on LC_MESSAGES, and peekCString decodes with the locale encoding. Probably acceptable, but it should be documented rather than described as a pure lookup. The proposal should also say what happens without HAVE_GAI_STRERROR, where the current code throws. Also, the underlying C function might return char pointers that live in a static buffer, like glibc’s generic fallback for unknown codes does: https://github.com/bminor/glibc/blob/master/posix/gai_strerror.c. The errno description for EAI_SYSTEM needs the same scrutiny.
    • "Nothing about resolving a name is thrown" overstates it. withSocketsDo can still fail on Windows, and the nullPtr check in followAddrInfo raises an IOException. Either list these or map them to Left.
    • errno must be read immediately after the C call, before any peek or allocation. GHC preserves errno per Haskell thread across safe calls, so this works, but it's worth stating as an implementation note.
    • The existing functions could improve cheaply too. Independently of this proposal, getAddrInfo and getNameInfo could put the errno of EAI_SYSTEM in ioe_errno and map EAI_AGAIN to a more fitting IOErrorType, as Give Winsock errors the IOErrorType of their errno #632 did for Winsock errors. That would help the many existing callers without any API change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions