I’d like to propose new constructor methods on Ipv4Addr and Ipv6Addr to create common addresses. This is intended to be a minor change to resolve a small papercut exposed by the recently added From
implementations on IpAddr
.
Specifically, I’d like to add the following to std::net::ip:
-
Ipv4Addr::localhost()
as a method that returnsIpv4Addr::new(127, 0, 0, 1)
. -
Ipv4Addr::unspecified()
as a method that returnsIpv4Addr::new(0, 0, 0, 0)
. -
Ipv6Addr::localhost()
as a method that returnsIpv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)
. -
Ipv6Addr::unspecified()
as a method that returnsIpv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)
.
It has been common practice (in example docs, and quick scripts) to use the FromStr
trait to quickly and readably create socket addresses: let sockaddr = "127.0.0.1:8000".parse().unwrap()
. Rust 1.16 created new From traits on IPAddr, allowing one to infallibly create socket addresses using syntax like let sockaddr = ([127, 0, 0, 1], 8000).into()
.
For IPv4 addresses, this new syntax introduces a slight hit to readability, but improves type safety, and removes the superfluous parsing step. For IPv6 addresses, however, they become significantly more awkward to type than the .parse()
d version, due to the lack of zero-elision. "[::1]:8000".parse().unwrap()
becomes ([0, 0, 0, 0, 0, 0, 0, 1], 8000).into()
, and "[::]:8000".parse().unwrap()
becomes ([0, 0, 0, 0, 0, 0, 0, 0], 8000).into()
.
Having an easy way to infallibly create SocketAddr
s is a clear win, but as a side effect, it makes it more awkward to create common IPv6 addresses than the equivalent legacy IPv4 addresses, which I think is unfortunate for a modern language.
The proposed additions: Ipv[46]Addr::localhost()
and Ipv[46]Addr::unspecified()
make both variants equally simple to create and easily readable without reintroducing fallibility or imposing an unnecessary parsing step.
Creating SocketAddrs using these would be as simple as
let sockaddr = (Ipv6Addr::unspecified(), 8000).into()
I considered as an alternative, a general syntax for infallibly eliding a sequence of zero-segments from Ipv6 addresses. It looked like this:
assert_eq!(([], [1]).into(), Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));
assert_eq!(([0xfe80, 0x540], [0xfabc]).into(), Ipv6Addr::new(0xfe80, 0x540, 0, 0, 0, 0, 0, 0xfabc));
assert_eq!(([], []).into(), Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0));
I don’t think it is either intuitive what the values represent, or a significant readability win over the existing options. It is also difficult to implement as the two arrays in each tuple can be a number of different sizes. Instead, I opted to solve the problem for the two most commonly used IP address types, which are also the ones that (in IpV6) suffer the most from the lack of zero-segment elision.
The unspecified()
name is chosen to match the existing is_unspecified()
method. The equivalent test for localhost()
is called is_loopback()
, but tests for a broader category of addresses than the single localhost()
address, so I chose to use a different, but obvious name.
The proposal is implemented in https://github.com/rust-lang/rust/pull/44395