pre-RFC: Convenience IP Address Constructors


#1

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 returns Ipv4Addr::new(127, 0, 0, 1).
  • Ipv4Addr::unspecified() as a method that returns Ipv4Addr::new(0, 0, 0, 0).
  • Ipv6Addr::localhost() as a method that returns Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1).
  • Ipv6Addr::unspecified() as a method that returns Ipv6Addr::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 SocketAddrs 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