Repository navigation
Description
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)- Result (getNameInfo: how are Bool arguments and Maybe results related? #496). A result is
Nothingexactly when itsBoolisFalse, andJustotherwise, but the type does not say so, and callers handle aNothingthat cannot happen. - Failure (The getNameInfo API is a little strange #416). Every
EAI_*code is raised as anIOException(NoSuchThing) with only a message. WithNI_NAMEREQD, a name which cannot be found is thrown rather than returned asNothing.
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: anEAI_*code becomes anIOExceptionwith 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 noEAI_SYSTEM: the returned code is the Winsock error itself, as inioe_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 asLeft; nothing about resolving a name is thrown. The only exception left is the one every function taking aSockAddrhas: aSockAddrUnixpath too long to marshal. NameInfoQueryis not in C: the arguments ofgetnameinfodiffer from getnameinfo(3) there, and its documentation will say so. It needsGADTs, which all supported compilers have.- Flags which do not apply to a query, such as
NI_NAMEREQDwithQueryService, are allowed and documented rather than ruled out by the types. - All of these are exported from
Network.Socket, next togetNameInfoandgetAddrInfo. - The API stays compatible, so this does not have to wait for v4.0 (v4.0 #557).
getNameInfoandgetAddrInfowill not be deprecated: they have far too many users.- A prototype of the types, including
EAI_SYSTEMcarrying itsErrno, compiles cleanly with-Walland is used as above.
Activity
[-]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[/+][-]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[/+]kazu-yamamoto commentedon Nov 13, 2026
@vdukhovni Any opinions on this issue?
peti commentedon Nov 13, 2026
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:
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:-1isEAI_BADFLAGS. All codes happen to exist there today becauseHsNet.hdefines_GNU_SOURCE, but on any glibc-like platform lacking one of them, that pattern would silently match a different, real error.EAI_OVERFLOWandEAI_ADDRFAMILYare both missing. With a shared sentinel they would compare equal andShowcould not name them correctly.Each missing code should get a distinct value outside the platform's real range, and the
Eq/Showbehaviour for that case should be stated.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) usesWSANO_DATA. If the values coincide on any supported toolchain,Left EAI_NODATAwould also catchEAI_NONAME, which arm wins would depend on arm order, andShowwould print one name for both. The table under "Error codes" should give the actual values per toolchain.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_NODATAandEAI_ADDRFAMILY.GaiErrorcan represent the same error two waysWith the raw constructors exported,
GaiError (#const EAI_SYSTEM)(anEAI_SYSTEMwithout an errno) andEAI_SYSTEM eare two forms of the same error that derivedEqtreats as different, andGaiError 0is 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 likegaiErrorCode :: 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; andErrnohas noShowinstance, soShow GaiErrorhas to be hand-written.Naming
This is the most debatable part.
getAddrInfoandgetaddrinfodiffer 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,Maybehints,NonEmpty). Practical side effects:Info.hscalready has local helpers namedgetaddrinfoandgetnameinfoinwhereclauses, which would shadow the new exports and trigger-Wallwarnings.getaddrinfounder that name and importNetwork.Socketunqualified 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 fromNetwork.Socket.Smaller points:
gaiErrorStringis not entirely pure. glibc'sgai_strerrorreturns translated messages depending onLC_MESSAGES, andpeekCStringdecodes 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 withoutHAVE_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.withSocketsDocan still fail on Windows, and thenullPtrcheck infollowAddrInforaises anIOException. Either list these or map them toLeft.peekor allocation. GHC preserves errno per Haskell thread across safe calls, so this works, but it's worth stating as an implementation note.getAddrInfoandgetNameInfocould put the errno ofEAI_SYSTEMinioe_errnoand mapEAI_AGAINto a more fittingIOErrorType, 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.