Skip to main content

Crate hostaddr

Crate hostaddr 

Source
Expand description

hostaddr

Type-safe, validated DNS domain names, hosts, and host addresses for Rust.

github LoC Build codecov

docs.rs crates.io crates.io license

Discord

§Overview

hostaddr provides type-safe, validated types for working with network host identifiers:

  • Domain<S> – A validated DNS domain name, with IDNA/punycode and percent-encoding support
  • Host<S> – Either a domain name or an IP address (v4/v6)
  • HostAddr<S> – A host with an optional port number (e.g. example.com:8080, [::1]:443)

All types are generic over their storage backend S, allowing you to choose the representation that best fits your use case – from zero-alloc stack buffers (Buffer) to shared smart pointers (Arc<str>).

Domain, Host, and HostAddr use representation-sensitive, case-sensitive Eq, Ord, and Hash implementations. DNS queries are commonly case-insensitive, but callers should normalize names explicitly before using them as identity keys when that is the desired policy.

§Release status

1.0.0 (2026-10-15) is the latest published stable release. It was promoted from the published 1.0.0-rc.3 validation candidate; production API, features, runtime dependencies, and wire semantics are unchanged. It:

  • validates the frozen host/address and IPC taxonomy APIs;
  • requires Rust 1.89 or newer;
  • keeps no_std/no-alloc and alloc feature combinations buildable;
  • documents the Serde representation and compatibility policy;
  • validates ASCII xn-- A-labels through UTS46 on allocating entry points; and
  • preserves the stack-only structural behavior of try_from_ascii_* and verify_ascii_domain.

§Features

  • no_std and no-alloc compatible: Use the Buffer type for stack-allocated domains without any heap allocation
  • Generic storage: Supports the storage types implemented by each address family, including String, Arc<str>, Box<str>, Vec<u8>, SmolStr, Bytes, and Buffer
  • IDNA/Punycode support: Automatic conversion of international domain names (e.g. 测试.中国 to punycode)
  • Type-safe validation: Domain names follow this crate’s ASCII and UTS46/IDNA validation policy
  • Percent-encoding: Transparent decoding of percent-encoded domains
  • IPv4/IPv6: Full support for IP addresses, including proper [::1]:port bracket syntax
  • Semantic IP/socket wrappers: LoopbackIpAddr/LoopbackAddr, PrivateIpAddr/PrivateAddr, and other validated address classes
  • Transport taxonomy: Addr, IpcAddr, and LocalAddr model host, IPC, and local-only addresses
  • Serde: Optional serialization/deserialization support
  • Fuzzing: Built-in arbitrary and quickcheck generators for property-based testing

§Installation

[dependencies]
hostaddr = "1"

§Feature Flags

FeatureDescription
std (default)Standard library support layered over alloc, enables IDNA and percent-decoding
vsock (default)Linux VM socket address support through VsockAddr and IpcAddr::Vsock
allocAllocation support without std
serdeSerialize/deserialize support
arbitraryFuzzing with arbitrary crate
quickcheckProperty-based generators for host builds; also compile-checked for wasm with the wasm_js backend
cheap-cloneCheapClone trait for smart pointer types
smol_strSmolStr storage support
bytesBytes storage support
triomphetriomphe::Arc storage support
tinyvecTinyVec storage support
smallvecSmallVec storage support

Only the features listed in this table are public. The old implicit idna feature was removed; enable alloc or std to use the crate’s IDNA support.

§Quick Start

#[cfg(feature = "std")]
{
use hostaddr::HostAddr;

// Domain with port
let addr: HostAddr<String> = "example.com:8080".parse().unwrap();
assert!(addr.is_domain());
assert_eq!(addr.port(), Some(8080));

// IPv4 with port
let addr: HostAddr<String> = "127.0.0.1:3000".parse().unwrap();
assert!(addr.is_ipv4());

// IPv6 with port (bracket syntax)
let addr: HostAddr<String> = "[::1]:443".parse().unwrap();
assert!(addr.is_ipv6());
assert_eq!(addr.to_string(), "[::1]:443");

// International domain names (auto punycode)
let addr: HostAddr<String> = "测试.中国:80".parse().unwrap();
}

§Address Taxonomy

Addr separates Internet host addresses from IPC transports while preserving generic storage for each address family. LocalAddr::Loopback is validated as a loopback IP socket address, while LocalAddr::Ipc groups non-IP IPC transport addresses without treating every IPC endpoint as a same-machine trust boundary.

Semantic address classes are available in IP-only and socket-with-port forms. The *IpAddr types wrap core::net::IpAddr; the matching *Addr types wrap core::net::SocketAddr and validate the socket IP component. Common classes include Loopback, Private, LinkLocal, Documentation, Benchmark, Shared, Multicast, Unspecified, and Broadcast.

The transparent IPC wrappers support borrowed DST storage such as &UnixAddr<Path> on Unix and &AbstractAddr<[u8]> on Linux. Windows named-pipe addresses are exposed through NamedPipeAddr; Linux VsockAddr support is available through the default-enabled vsock feature.

IPC values are representation wrappers only. Constructing one does not open a socket, inspect the filesystem, normalize a platform pathname, or prove that an endpoint is local or trusted. Callers must perform any OS-level validation and security checks required by their application.

For Linux AbstractAddr, the payload is interpreted as the name after the kernel’s leading-NUL marker. The wrapper stores the supplied bytes unchanged: it does not add, remove, or reject leading or interior NUL bytes, and as_bytes returns the original payload.

HostAddr conversions from SocketAddrV6 retain the IPv6 address and port but discard flowinfo and scope_id, because HostAddr stores only an IP address and optional port. Keep the original SocketAddrV6 when those fields matter.

The parsing and TryFrom entry points, plus From<Domain<_>>, validate domain invariants. The allocating domain parsers validate ASCII xn-- A-labels with UTS46; the try_from_ascii_*, verify_ascii_domain, and verify_ascii_domain_allow_percent_encoding APIs are deliberately structural and do not decode ACE payloads. Address constructors that accept caller-provided storage are documented with their own invariant requirements; storage support is limited to the types and trait combinations implemented by each address family.

§Serde compatibility

Serde is optional. Human-readable formats use named variants (host, ipc, loopback) and textual IPC tags (unix, abstract, named_pipe, vsock). Binary formats use two-element tuples and stable numeric tags:

TypeTags
IpcAddrunix = 0, abstract = 1, named_pipe = 2, vsock = 3
Addrhost = 0, ipc = 1
LocalAddrloopback = 0, ipc = 1

These tags and variant names are part of the 1.0.0 wire-format contract. New variants must not reuse an existing tag; deserialization also re-checks the validated domain and semantic-address invariants.

The stable 1.0.0 semantic socket writers use the RC2 object forms and readers accept RC1 strings and legacy binary tags. RC1 readers cannot read the stable object forms or binary tag 2; metadata omitted by an RC1 writer cannot be reconstructed.

Semantic IP and socket wrappers use the following data model and Serde contract:

ModelValidated dataHuman-readable writerHuman-readable readerBinary representation
*IpAddrIpAddrIP stringIP stringexisting IpAddr representation
*Addr V4SocketAddrV4stable RC2 object {"v4":{"ip":"...","port":...}}stable object or RC1 stringtag 0: V4(ip, port)
*Addr V6SocketAddrV6stable RC2 object {"v6":{"ip":"...","port":...,"flowinfo":...,"scope_id":...}}stable object or RC1 stringtag 1: legacy V6(ip, port); tag 2: V6Extended(ip, port, flowinfo, scope_id)

Stable 1.0.0 readers accept RC1 socket strings and legacy binary tags. RC1 cannot read the stable object forms or binary tag 2. RC1 strings and legacy binary tag 1 do not carry IPv6 flowinfo or scope_id, so stable readers read them as zero; metadata already lost by an RC1 writer cannot be reconstructed.

use hostaddr::{Addr, HostAddr, LocalAddr, LoopbackAddr, PrivateIpAddr};

let host = HostAddr::<&str>::from(("127.0.0.1".parse::<core::net::IpAddr>().unwrap(), 8080));
let addr: Addr<&str, &str, &[u8]> = host.into();

let private = PrivateIpAddr::try_from("10.0.0.1".parse::<core::net::IpAddr>().unwrap()).unwrap();

#[cfg(unix)]
{
  use hostaddr::{IpcAddr, UnixAddr};

  let unix: IpcAddr<&str, &[u8]> = UnixAddr::new("/tmp/app.sock").into();
}

let local_ip = LoopbackAddr::try_from("127.0.0.1:8080".parse::<core::net::SocketAddr>().unwrap()).unwrap();
let local: LocalAddr<&str, &[u8]> = local_ip.into();

§Examples

§Working with Domains

#[cfg(feature = "std")]
{
use hostaddr::Domain;

// Validated at construction -- invalid names are rejected
assert!(Domain::<String>::try_from("example.com").is_ok());
assert!(Domain::<String>::try_from("-invalid.com").is_err());

// International domain names are converted to punycode
let domain: Domain<String> = "测试.中国".parse().unwrap();
assert_eq!(domain.as_inner().as_str(), "xn--0zwm56d.xn--fiqs8s");

// FQDN support
let domain: Domain<String> = "example.com.".parse().unwrap();
assert!(domain.is_fqdn());

// ASCII-only fast path (no punycode or percent-decoding)
let domain = Domain::try_from_ascii_str("example.com").unwrap();
}

Domain parsing is independent of URL-specialness: allocating try_from/verify_domain entry points decode percent-encoded octets and then apply the same UTS46/ASCII policy, while try_from_ascii_*, verify_ascii_domain, and verify_ascii_domain_allow_percent_encoding accept only structural ASCII input. A ParseDomainError therefore means the supplied domain representation is invalid; it is not a URL-parser error and does not perform URL authority parsing.

§Choosing a Storage Type

#[cfg(feature = "std")]
{
use hostaddr::HostAddr;
use std::sync::Arc;

// Heap-allocated String
let addr: HostAddr<String> = "example.com:8080".parse().unwrap();

// Shared ownership with Arc<str>
let addr: HostAddr<Arc<str>> = "example.com:8080".parse().unwrap();

// Byte storage
let addr: HostAddr<Vec<u8>> = "example.com".parse().unwrap();
}

§Stack-Allocated Domains (no_std / no-alloc)

use hostaddr::{HostAddr, Buffer, Domain};

// No heap allocation -- 255-byte stack buffer
let domain: Domain<Buffer> = Domain::try_from("example.com").unwrap();
let addr: HostAddr<Buffer> = HostAddr::try_from("example.com:443").unwrap();

§Manipulating Addresses

#[cfg(feature = "std")]
{
use hostaddr::HostAddr;

let mut addr: HostAddr<String> = "example.com".parse().unwrap();
addr.set_port(8080);
assert_eq!(addr.to_string(), "example.com:8080");

// Builder-style with default port
let addr = "example.com".parse::<HostAddr<String>>().unwrap()
    .with_default_port(443);
assert_eq!(addr.port(), Some(443));
}

§Verification Functions

#[cfg(any(feature = "std", feature = "alloc"))]
{
use hostaddr::{verify_domain, verify_ascii_domain};

assert!(verify_domain(b"example.com").is_ok());
assert!(verify_domain("测试.中国".as_bytes()).is_ok());

assert!(verify_ascii_domain(b"example.com").is_ok());
assert!(verify_ascii_domain("测试.中国".as_bytes()).is_err());
}
§License

hostaddr is under the terms of both the MIT license and the Apache License (Version 2.0).

See LICENSE-APACHE, LICENSE-MIT for details.

Copyright (c) 2026 Al Liu.

Structs§

AbstractAddrLinux
A Linux abstract socket name payload.
BenchmarkAddr
A socket address whose IP address is reserved for benchmarking.
BenchmarkIpAddr
An IP address reserved for benchmarking.
BroadcastAddr
A socket address whose IP address is the IPv4 limited broadcast address.
BroadcastIpAddr
The IPv4 limited broadcast address.
Buffer
An immutable buffer which contains a valid domain.
DocumentationAddr
A socket address whose IP address is reserved for documentation or examples.
DocumentationIpAddr
An IP address reserved for documentation or examples.
Domain
A DNS domain name, as . dot-separated labels. Non-ASCII labels are converted to ASCII using UTS46/IDNA. Allocating parsing entry points also decode percent-encoded input before applying that policy.
HostAddr
A host address, which consists of a Host and an optional port number.
LinkLocalAddr
A socket address whose IP address is link-local.
LinkLocalIpAddr
An IPv4 or IPv6 link-local address.
LoopbackAddr
A socket address whose IP address is loopback.
LoopbackIpAddr
An IP address in the loopback range.
MulticastAddr
A socket address whose IP address is multicast.
MulticastIpAddr
An IPv4 or IPv6 multicast address.
ParseAsciiDomainError
The provided input could not be parsed because it is not an ASCII syntactically-valid DNS Domain.
ParseAsciiHostError
An error which can be returned when parsing an ASCII Host.
ParseBenchmarkAddrError
The provided socket address is not reserved for benchmarking.
ParseBenchmarkIpAddrError
The provided IP address is not reserved for benchmarking.
ParseBroadcastAddrError
The provided socket address is not the IPv4 limited broadcast address.
ParseBroadcastIpAddrError
The provided IP address is not the IPv4 limited broadcast address.
ParseDocumentationAddrError
The provided socket address is not reserved for documentation.
ParseDocumentationIpAddrError
The provided IP address is not reserved for documentation.
ParseDomainError
The provided input could not be parsed because it is not a syntactically-valid DNS Domain.
ParseHostError
An error which can be returned when parsing a Host.
ParseLinkLocalAddrError
The provided socket address is not link-local.
ParseLinkLocalIpAddrError
The provided IP address is not link-local.
ParseLoopbackAddrError
The provided socket address is not a loopback address.
ParseLoopbackIpAddrError
The provided IP address is not a loopback address.
ParseMulticastAddrError
The provided socket address is not multicast.
ParseMulticastIpAddrError
The provided IP address is not multicast.
ParsePrivateAddrError
The provided socket address is not private-use or unique-local.
ParsePrivateIpAddrError
The provided IP address is not private-use or unique-local.
ParseSharedAddrError
The provided socket address is not in IPv4 shared address space.
ParseSharedIpAddrError
The provided IP address is not in IPv4 shared address space.
ParseUnspecifiedAddrError
The provided socket address is not the exact unspecified address.
ParseUnspecifiedIpAddrError
The provided IP address is not the exact unspecified address.
PrivateAddr
A socket address whose IP address is private-use or unique-local.
PrivateIpAddr
An RFC 1918 IPv4 private-use or RFC 4193 IPv6 unique-local address.
SharedAddr
A socket address whose IP address is in IPv4 shared address space.
SharedIpAddr
An IPv4 shared address space address.
TryUnwrapError
Error returned by the derived TryUnwrap implementation.
UnixAddrUnix
A Unix-domain socket pathname.
UnspecifiedAddr
A socket address whose IP address is the exact unspecified address.
UnspecifiedIpAddr
The exact IPv4 or IPv6 unspecified address.
VsockAddrLinux and vsock
A VM socket address.

Enums§

Addr
Any address accepted by a runtime.
Host
The host name.
IpcAddr
A non-IP communication address.
LocalAddr
An address in the local-address taxonomy.
ParseAsciiHostAddrError
An error which can be returned when parsing a HostAddr.
ParseHostAddrError
An error which can be returned when parsing a HostAddr.

Functions§

verify_ascii_domain
Verifies that the input is a valid ASCII domain name.
verify_ascii_domain_allow_percent_encoding
Verifies that the input is a valid ASCII domain name. The input can be a percent-encoded domain name.
verify_domainalloc or std
Verifies that the input is a valid domain name.