Skip to content

ACP: Support NonZeroU16 in ToSocketAddrs; SocketAddr's From Implementation #781

@edward-shen

Description

@edward-shen

Proposal

Thank you for your time and consideration!

Problem statement

core::net::SocketAddr's blanket implementation only accepts u16 as a port type, not NonZeroU16.

Motivating examples or use cases

For services, a non-zero port is often provided as an CLI argument to specify a port to consistently listen to. Common CLI parsers, like clap, support accepting NonZeroU16s as a strongly typed argument. I would like to provide these NonZeroU16s directly to TcpListener::bind which accepts any ToSocketAddrs, and for consistency, SocketAddr to have a From<(I, NonZeroU16>.

Here is minimal example of the desired state:

use std::net::IpAddr;
use std::net::Ipv4Addr;
use std::net::TcpListener;
use std::num::NonZeroU16;

use clap::Parser;

#[derive(clap::Parser)]
struct Args {
    #[clap(short, long, default_value_t = IpAddr::V4(Ipv4Addr::new(0,0,0,0)))]
    address: IpAddr,
    #[clap(short, long, default_value_t = const { NonZeroU16::new(9001).unwrap() })]
    port: NonZeroU16,
}

fn main() {
    let args = Args::parse();

    // Desired
    let _listener = TcpListener::bind((args.address, args.port)).unwrap();
    // Current
    let _listener = TcpListener::bind((args.address, args.port.get())).unwrap();
}

Solution sketch

The solution I would see would be to add a NonZeroU16 variant for all tuple implementations that accept a u16. As an example:

impl ToSocketAddrs for (&str, NonZeroU16) {
    type Iter = <(&str, u16) as ToSocketAddrs>::Iter;

    fn to_socket_addrs(&self) -> io::Result<Self::Iter> {
        (self.0, self.1.get()).to_socket_addrs()
    }
}

Similar impls would be created for the following tuples:

  • (IpAddr, u16)
  • (String u16)
  • (Ipv4Addr, u16)
  • (Ipv6Addr, u16)

Additionally, SocketAddr for consistency should similarly have a NonZeroU16 trait implemented:

impl<I: [const] Into<IpAddr>> const From<(I, NonZeroU16)> for SocketAddr {
    fn from(pieces: (I, NonZeroU16)) -> SocketAddr {
        SocketAddr::new(pieces.0.into(), pieces.1.get())
    }
}

Alternatives

As currently presented, this is a minor ergonomic feature with limited scope. The alternative is to call .get() on the NonZeroU16, and no changes would be needed. However, I think this makes for more consistency library experience.

I considered suggesting a blanket implementation where we accept Into<u16>, but I was concerned that it would be too widely applicable so I did not propose that.

I think an interesting point is that the port for SocketAddrs can be 0: the semantics (for Linux at least) is to have the system pick a random port. I think supporting NonZeroU16 still makes sense despite this because it is a strict subset of u16.

This does ask the question of "Should there be a SocketAddr::new_nonzero constructor?" I don't think the library has existing precedence on adding NonZero constructors, so I did not include that as part of the proposal.

Links and related work

(Left empty)

What happens now?

This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.

Possible responses

The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):

  • We think this problem seems worth solving, and the standard library might be the right place to solve it.
  • We think that this probably doesn't belong in the standard library.

Second, if there's a concrete solution:

  • We think this specific solution looks roughly right, approved, you or someone else should implement this. (Further review will still happen on the subsequent implementation PR.)
  • We're not sure this is the right solution, and the alternatives or other materials don't give us enough information to be sure about that. Here are some questions we have that aren't answered, or rough ideas about alternatives we'd want to see discussed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    T-libs-apiapi-change-proposalA proposal to add or alter unstable APIs in the standard libraries

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions