diff --git a/library/std/src/net/mod.rs b/library/std/src/net/mod.rs
index 01e3db9de51c4..bd4866e5bc784 100644
--- a/library/std/src/net/mod.rs
+++ b/library/std/src/net/mod.rs
@@ -28,7 +28,9 @@ pub use self::ip_addr::{IpAddr, Ipv4Addr, Ipv6Addr, Ipv6MulticastScope};
 #[stable(feature = "rust1", since = "1.0.0")]
 pub use self::parser::AddrParseError;
 #[stable(feature = "rust1", since = "1.0.0")]
-pub use self::socket_addr::{SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs};
+pub use self::socket_addr::{
+    SocketAddr, SocketAddrFamily, SocketAddrV4, SocketAddrV6, ToSocketAddrs,
+};
 #[unstable(feature = "tcplistener_into_incoming", issue = "88339")]
 pub use self::tcp::IntoIncoming;
 #[stable(feature = "rust1", since = "1.0.0")]
@@ -36,8 +38,12 @@ pub use self::tcp::{Incoming, TcpListener, TcpStream};
 #[stable(feature = "rust1", since = "1.0.0")]
 pub use self::udp::UdpSocket;
 
+#[unstable(feature = "unbound_socket", issue = "none")]
+pub use self::udp::UnboundUdpSocket;
+
 mod display_buffer;
 mod ip_addr;
+
 mod parser;
 mod socket_addr;
 mod tcp;
diff --git a/library/std/src/net/socket_addr.rs b/library/std/src/net/socket_addr.rs
index 33b0dfa03e0ed..6bd537106d3ed 100644
--- a/library/std/src/net/socket_addr.rs
+++ b/library/std/src/net/socket_addr.rs
@@ -264,6 +264,15 @@ impl SocketAddr {
     pub const fn is_ipv6(&self) -> bool {
         matches!(*self, SocketAddr::V6(_))
     }
+
+    /// Returns the `SocketAddrFamily` value of this `SocketAddr`.
+    #[unstable(feature = "unbound_socket", issue = "none")]
+    pub const fn family(&self) -> SocketAddrFamily {
+        match *self {
+            SocketAddr::V4(..) => SocketAddrFamily::InetV4,
+            SocketAddr::V6(..) => SocketAddrFamily::InetV6,
+        }
+    }
 }
 
 impl SocketAddrV4 {
@@ -972,3 +981,14 @@ impl ToSocketAddrs for String {
         (&**self).to_socket_addrs()
     }
 }
+
+/// Address family values for an Internet socket address.
+#[derive(Debug, Clone, Copy, PartialEq)]
+#[non_exhaustive]
+#[unstable(feature = "unbound_socket", issue = "none")]
+pub enum SocketAddrFamily {
+    /// Address family of IPv4.
+    InetV4,
+    /// Address family of IPv6.
+    InetV6,
+}
diff --git a/library/std/src/net/udp.rs b/library/std/src/net/udp.rs
index 864e1b0f3450a..1975746482f1f 100644
--- a/library/std/src/net/udp.rs
+++ b/library/std/src/net/udp.rs
@@ -3,7 +3,7 @@ mod tests;
 
 use crate::fmt;
 use crate::io::{self, ErrorKind};
-use crate::net::{Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs};
+use crate::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrFamily, ToSocketAddrs};
 use crate::sys_common::net as net_imp;
 use crate::sys_common::{AsInner, FromInner, IntoInner};
 use crate::time::Duration;
@@ -811,3 +811,100 @@ impl fmt::Debug for UdpSocket {
         self.0.fmt(f)
     }
 }
+
+/// A UDP Socket that is not bound to a `SocketAddr` yet.
+///
+/// This socket is designed to support socket configurations _before_
+/// binding to a `SocketAddr`. After configurations, this socket
+/// can be bound and translated into a [`UdpSocket`].
+///
+/// # Example
+///
+/// ```no_run
+/// #![feature(unbound_socket)]
+/// use std::net::{SocketAddr, SocketAddrFamily, UnboundUdpSocket};
+///
+/// fn main() -> std::io::Result<()> {
+///     let unbound_socket = UnboundUdpSocket::new(SocketAddrFamily::InetV4)?;
+///     unbound_socket.set_reuseaddr(true)?;
+///     let addr = SocketAddr::from(([127, 0, 0, 1], 5500));
+///     let _udp_socket = unbound_socket.bind(&addr)?;
+///     Ok(())
+/// }
+/// ```
+#[unstable(feature = "unbound_socket", issue = "none")]
+pub struct UnboundUdpSocket {
+    inner: net_imp::UnboundUdpSocket,
+}
+
+impl UnboundUdpSocket {
+    /// Creates a new unbound UDP socket with `addr_family`.
+    #[unstable(feature = "unbound_socket", issue = "none")]
+    pub fn new(addr_family: SocketAddrFamily) -> io::Result<UnboundUdpSocket> {
+        let inner = net_imp::UnboundUdpSocket::new(addr_family)?;
+        Ok(Self { inner })
+    }
+
+    /// Sets `SO_REUSEADDR` option for the socket.
+    ///
+    /// In general, this option allows a second caller to bind to a `(addr, port)` again,
+    /// where the `addr` could be the `unspecified` address. However it behaves with subtle
+    /// differences on different platforms. Please be sure to check your platform for
+    /// the exact expected behaviors.
+    ///
+    /// This method can only be called before `bind`, otherwise will fail.
+    #[unstable(feature = "unbound_socket", issue = "none")]
+    pub fn set_reuseaddr(&self, enable: bool) -> io::Result<()> {
+        self.inner.set_reuseaddr(enable)
+    }
+
+    /// Sets `SO_REUSEPORT` option for the socket.
+    ///
+    /// In general, this option allows a second caller to bind to a same `(addr, port)`
+    /// pair again, if the first caller has enabled this option too. Please check with
+    /// your specific platform for the details of the behavior.
+    ///
+    /// This option is only available for UNIX-like platforms and not Windows platforms.
+    #[unstable(feature = "unbound_socket", issue = "none")]
+    pub fn set_reuseport(&self, enable: bool) -> io::Result<()> {
+        self.inner.set_reuseport(enable)
+    }
+
+    /// Sets `SO_EXCLUSIVEADDRUSE` option for the socket.
+    ///
+    /// This option is only available in Windows. Its purpose is to prevent
+    /// any other caller to "reuse" the same (addr, port), even if they call
+    /// `set_reuseaddr(true)`. This method returns an error on non-Windows platforms.
+    #[unstable(feature = "unbound_socket", issue = "none")]
+    pub fn set_exclusiveaddruse(&self, enable: bool) -> io::Result<()> {
+        self.inner.set_exclusiveaddruse(enable)
+    }
+
+    /// Binds to `addr`, consumes this unbound socket and returns a [`UdpSocket`].
+    #[unstable(feature = "unbound_socket", issue = "none")]
+    pub fn bind(self, addr: &SocketAddr) -> io::Result<UdpSocket> {
+        let net_imp = self.inner.bind(addr)?;
+        Ok(UdpSocket(net_imp))
+    }
+}
+
+#[unstable(feature = "unbound_socket", issue = "none")]
+impl fmt::Debug for UnboundUdpSocket {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        self.inner.fmt(f)
+    }
+}
+
+#[unstable(feature = "unbound_socket", issue = "none")]
+impl AsInner<net_imp::UnboundUdpSocket> for UnboundUdpSocket {
+    fn as_inner(&self) -> &net_imp::UnboundUdpSocket {
+        &self.inner
+    }
+}
+
+#[unstable(feature = "unbound_socket", issue = "none")]
+impl IntoInner<net_imp::UnboundUdpSocket> for UnboundUdpSocket {
+    fn into_inner(self) -> net_imp::UnboundUdpSocket {
+        self.inner
+    }
+}
diff --git a/library/std/src/net/udp/tests.rs b/library/std/src/net/udp/tests.rs
index f82904ffbbf77..96b58e75d861d 100644
--- a/library/std/src/net/udp/tests.rs
+++ b/library/std/src/net/udp/tests.rs
@@ -363,3 +363,269 @@ fn set_nonblocking() {
         }
     })
 }
+
+#[cfg(not(windows))]
+#[test]
+fn set_reuseaddr_v4_not_windows() {
+    let addr = next_test_ip4();
+    let addr_family = addr.family();
+    let port = addr.port();
+    let wild_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), port);
+
+    let unbound_socket = t!(UnboundUdpSocket::new(addr_family));
+    t!(unbound_socket.set_reuseaddr(true)); // Needed at least in Linux.
+    let wild_socket = t!(unbound_socket.bind(&wild_addr));
+
+    // Without set_reuseaddr, we cannot bind to the addr with the same port.
+    let unbound_socket2 = t!(UnboundUdpSocket::new(addr_family));
+    let res = unbound_socket2.bind(&addr);
+    assert!(res.is_err());
+
+    // With set_reuseaddr(false), we cannot bind with the same port.
+    let unbound_socket3 = t!(UnboundUdpSocket::new(addr_family));
+    t!(unbound_socket3.set_reuseaddr(false));
+    let res = unbound_socket3.bind(&addr);
+    assert!(res.is_err());
+
+    // With set_reuseaddr(true), we can bind to a local addr with the same port.
+    let unbound_socket4 = t!(UnboundUdpSocket::new(addr_family));
+    t!(unbound_socket4.set_reuseaddr(true));
+    let socket = t!(unbound_socket4.bind(&addr));
+
+    const MSG_1: &[u8] = b"hello world";
+    t!(socket.send_to(MSG_1, &addr));
+    let mut buf = [0; MSG_1.len()];
+    let (size, _) = t!(socket.recv_from(&mut buf));
+    assert_eq!(MSG_1, &buf[..]);
+    assert_eq!(size, MSG_1.len());
+
+    // Multicast also works with set_reuseaddr.
+    let group_ip = Ipv4Addr::new(224, 0, 0, 251);
+    let any_ip = Ipv4Addr::new(0, 0, 0, 0);
+    let broadcast_addr = SocketAddr::new(IpAddr::V4(group_ip), port);
+
+    let unbound_socket_mcast = t!(UnboundUdpSocket::new(addr_family));
+    t!(unbound_socket_mcast.set_reuseaddr(true));
+    let socket_mcast = t!(unbound_socket_mcast.bind(&broadcast_addr));
+    t!(socket_mcast.join_multicast_v4(&group_ip, &any_ip));
+    t!(wild_socket.join_multicast_v4(&group_ip, &any_ip));
+
+    const MSG_2: &[u8] = b"hello multicast";
+    t!(wild_socket.send_to(MSG_2, &broadcast_addr));
+    let mut buf = [0; MSG_2.len()];
+    let (size, _) = t!(socket_mcast.recv_from(&mut buf));
+    assert_eq!(MSG_2, &buf[..]);
+    assert_eq!(size, MSG_2.len());
+}
+
+#[cfg(not(windows))]
+#[test]
+fn set_reuseaddr_v6_not_windows() {
+    let addr = next_test_ip6();
+    let addr_family = addr.family();
+    let port = addr.port();
+    let wild_addr = SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)), port);
+
+    let unbound_socket = t!(UnboundUdpSocket::new(addr_family));
+    t!(unbound_socket.set_reuseaddr(true));
+    let _wild_socket = t!(unbound_socket.bind(&wild_addr));
+
+    // Without set_reuseaddr, we cannot bind to the addr with the same port.
+    let unbound_socket2 = t!(UnboundUdpSocket::new(addr_family));
+    let res = unbound_socket2.bind(&addr);
+    assert!(res.is_err());
+
+    // With set_reuseaddr(false), we cannot bind with the same port.
+    let unbound_socket3 = t!(UnboundUdpSocket::new(addr_family));
+    t!(unbound_socket3.set_reuseaddr(false));
+    let res = unbound_socket3.bind(&addr);
+    assert!(res.is_err());
+
+    // With set_reuseaddr(true), we can bind to a local addr with the same port.
+    let unbound_socket4 = t!(UnboundUdpSocket::new(addr_family));
+    t!(unbound_socket4.set_reuseaddr(true));
+    let socket = t!(unbound_socket4.bind(&addr));
+
+    const MSG_1: &[u8] = b"hello world";
+    t!(socket.send_to(MSG_1, &addr));
+    let mut buf = [0; MSG_1.len()];
+    let (size, _) = t!(socket.recv_from(&mut buf));
+    assert_eq!(MSG_1, &buf[..]);
+    assert_eq!(size, MSG_1.len());
+}
+
+#[cfg(windows)]
+#[test]
+fn set_reuseaddr_v4_windows() {
+    let addr = next_test_ip4();
+    let addr_family = addr.family();
+
+    // Since Windows Server 2003 and later, we cannot bind to the same specific address & port
+    // unless both the first socket and the second socket enables set_reuseaddr. Note that
+    // wild card address (0.0.0.0) is not subject to this rule.
+
+    // The 2nd bind would fail as the 1st socket did not enable set_reuseaddr.
+    let unbound_socket1 = t!(UnboundUdpSocket::new(addr_family));
+    let _socket1 = t!(unbound_socket1.bind(&addr));
+
+    let unbound_socket2 = t!(UnboundUdpSocket::new(addr_family));
+    let res = unbound_socket2.bind(&addr);
+    assert!(res.is_err());
+
+    let unbound_socket2 = t!(UnboundUdpSocket::new(addr_family));
+    t!(unbound_socket2.set_reuseaddr(true)); // only the 2nd socket enables reuseaddr.
+    let res = unbound_socket2.bind(&addr);
+    assert!(res.is_err());
+
+    // The 2nd bind would succeed as both the 1st socket and the 2nd socket enabled
+    // set_reuseaddr.
+    let addr = next_test_ip4();
+    let addr_family = addr.family();
+
+    let unbound_socket1 = t!(UnboundUdpSocket::new(addr_family));
+    t!(unbound_socket1.set_reuseaddr(true));
+    let _socket1 = t!(unbound_socket1.bind(&addr));
+
+    let unbound_socket2 = t!(UnboundUdpSocket::new(addr_family));
+    t!(unbound_socket2.set_reuseaddr(true));
+    let _socket2 = t!(unbound_socket2.bind(&addr));
+}
+
+#[cfg(windows)]
+#[test]
+fn set_reuseaddr_v6_windows() {
+    let addr = next_test_ip6();
+    let addr_family = addr.family();
+
+    // Since Windows Server 2003 and later, we cannot bind to the same specific address & port
+    // unless both the first socket and the second socket enables set_reuseaddr. Note that
+    // wild card address (0.0.0.0) is not subject to this rule.
+
+    // The 2nd bind would fail as the 1st socket did not enable set_reuseaddr.
+    let unbound_socket1 = t!(UnboundUdpSocket::new(addr_family));
+    let _socket1 = t!(unbound_socket1.bind(&addr));
+
+    let unbound_socket2 = t!(UnboundUdpSocket::new(addr_family));
+    let res = unbound_socket2.bind(&addr);
+    assert!(res.is_err());
+
+    let unbound_socket2 = t!(UnboundUdpSocket::new(addr_family));
+    t!(unbound_socket2.set_reuseaddr(true)); // only the 2nd socket enables reuseaddr.
+    let res = unbound_socket2.bind(&addr);
+    assert!(res.is_err());
+
+    // The 2nd bind would succeed as both the 1st socket and the 2nd socket enabled
+    // set_reuseaddr.
+    let addr = next_test_ip6();
+    let addr_family = addr.family();
+
+    let unbound_socket1 = t!(UnboundUdpSocket::new(addr_family));
+    t!(unbound_socket1.set_reuseaddr(true));
+    let _socket1 = t!(unbound_socket1.bind(&addr));
+
+    let unbound_socket2 = t!(UnboundUdpSocket::new(addr_family));
+    t!(unbound_socket2.set_reuseaddr(true));
+    let _socket2 = t!(unbound_socket2.bind(&addr));
+}
+
+#[cfg(unix)]
+#[test]
+fn set_reuseport_v4_unix() {
+    set_reuseport_unix(next_test_ip4());
+}
+
+#[cfg(unix)]
+#[test]
+fn set_reuseport_v6_unix() {
+    set_reuseport_unix(next_test_ip6());
+}
+
+#[cfg(unix)]
+fn set_reuseport_unix(sockaddr: SocketAddr) {
+    let addr_family = sockaddr.family();
+
+    // Bind the 1st socket.
+    let unbound_socket = t!(UnboundUdpSocket::new(addr_family));
+    t!(unbound_socket.set_reuseport(true));
+    let socket1 = t!(unbound_socket.bind(&sockaddr));
+
+    // With set_reuseport, We can bind again to the same sockaddr.
+    let unbound_socket2 = t!(UnboundUdpSocket::new(addr_family));
+    t!(unbound_socket2.set_reuseport(true));
+    let socket2 = t!(unbound_socket2.bind(&sockaddr));
+
+    // Use the new socket to send. Because the recv side
+    // is distributed between two sockets by the OS, we cannot
+    // be sure which socket to recv_from, and hence not to recv
+    // the packet.
+    const MSG_1: &[u8] = b"hello world";
+    t!(socket1.send_to(MSG_1, &sockaddr));
+    t!(socket2.send_to(MSG_1, &sockaddr));
+
+    // Verify the negative case:
+    // Without set_reuseport, We cannot bind again to the same sockaddr.
+    let unbound_socket3 = t!(UnboundUdpSocket::new(addr_family));
+    let res = unbound_socket3.bind(&sockaddr);
+    assert!(res.is_err());
+
+    // Make sure the 1st socket was not dropped earlier.
+    t!(socket1.send_to(MSG_1, &sockaddr));
+}
+
+#[cfg(windows)]
+#[test]
+fn set_exclusiveaddruse_v4_windows() {
+    let addr = next_test_ip4();
+    let addr_family = addr.family();
+    let port = addr.port();
+    let wild_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), port);
+
+    let unbound_socket = t!(UnboundUdpSocket::new(addr_family));
+    t!(unbound_socket.set_exclusiveaddruse(true));
+    let _wild_socket = t!(unbound_socket.bind(&wild_addr));
+
+    // With set_exclusiveaddruse(true), we cannot bind to the addr with the same port,
+    // even the first socket is a wild adress.
+    let unbound_socket2 = t!(UnboundUdpSocket::new(addr_family));
+    t!(unbound_socket2.set_reuseaddr(true));
+    let res = unbound_socket2.bind(&addr);
+    assert!(res.is_err());
+}
+
+#[cfg(windows)]
+#[test]
+fn set_exclusiveaddruse_v6_windows() {
+    let addr = next_test_ip6();
+    let addr_family = addr.family();
+    let port = addr.port();
+    let wild_addr = SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)), port);
+
+    let unbound_socket = t!(UnboundUdpSocket::new(addr_family));
+    t!(unbound_socket.set_exclusiveaddruse(true));
+    let _wild_socket = t!(unbound_socket.bind(&wild_addr));
+
+    // With set_exclusiveaddruse(true), we cannot bind to the addr with the same port.
+    let unbound_socket2 = t!(UnboundUdpSocket::new(addr_family));
+    t!(unbound_socket2.set_reuseaddr(true));
+    let res = unbound_socket2.bind(&addr);
+    assert!(res.is_err());
+}
+
+#[cfg(not(windows))]
+#[test]
+fn set_exclusiveaddruse_non_windows() {
+    let addr_family = SocketAddrFamily::InetV4;
+    let unbound_socket = t!(UnboundUdpSocket::new(addr_family));
+    let res = unbound_socket.set_exclusiveaddruse(true);
+    assert!(res.is_err()); // Not supported.
+}
+
+#[test]
+fn bind_wrong_addr_family() {
+    // An UnboundUdpSocket of IPv4 cannot bind to IPv6 address.
+    let addr = next_test_ip6();
+    let addr_family = SocketAddrFamily::InetV4;
+    let unbound_socket = t!(UnboundUdpSocket::new(addr_family));
+    let res = unbound_socket.bind(&addr);
+    assert!(res.is_err());
+}
diff --git a/library/std/src/os/fd/owned.rs b/library/std/src/os/fd/owned.rs
index c16518577f7c4..1441c4da2cc5d 100644
--- a/library/std/src/os/fd/owned.rs
+++ b/library/std/src/os/fd/owned.rs
@@ -454,3 +454,19 @@ impl<'a> AsFd for io::StderrLock<'a> {
         unsafe { BorrowedFd::borrow_raw(2) }
     }
 }
+
+#[unstable(feature = "unbound_socket", issue = "none")]
+impl AsFd for crate::net::UnboundUdpSocket {
+    #[inline]
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.as_inner().socket().as_fd()
+    }
+}
+
+#[unstable(feature = "unbound_socket", issue = "none")]
+impl From<crate::net::UnboundUdpSocket> for OwnedFd {
+    #[inline]
+    fn from(socket: crate::net::UnboundUdpSocket) -> OwnedFd {
+        socket.into_inner().into_socket().into_inner().into_inner().into()
+    }
+}
diff --git a/library/std/src/os/windows/io/raw.rs b/library/std/src/os/windows/io/raw.rs
index 49e4f304f5dba..3021e6ec118c2 100644
--- a/library/std/src/os/windows/io/raw.rs
+++ b/library/std/src/os/windows/io/raw.rs
@@ -255,6 +255,14 @@ impl AsRawSocket for net::UdpSocket {
     }
 }
 
+#[unstable(feature = "unbound_socket", issue = "none")]
+impl AsRawSocket for net::UnboundUdpSocket {
+    #[inline]
+    fn as_raw_socket(&self) -> RawSocket {
+        self.as_inner().socket().as_raw_socket()
+    }
+}
+
 #[stable(feature = "from_raw_os", since = "1.1.0")]
 impl FromRawSocket for net::TcpStream {
     #[inline]
@@ -303,3 +311,11 @@ impl IntoRawSocket for net::UdpSocket {
         self.into_inner().into_socket().into_inner().into_raw_socket()
     }
 }
+
+#[unstable(feature = "unbound_socket", issue = "none")]
+impl IntoRawSocket for net::UnboundUdpSocket {
+    #[inline]
+    fn into_raw_socket(self) -> RawSocket {
+        self.into_inner().into_socket().into_inner().into_raw_socket()
+    }
+}
diff --git a/library/std/src/os/windows/io/socket.rs b/library/std/src/os/windows/io/socket.rs
index 72cb3406dcada..b4e4fb7b67e6a 100644
--- a/library/std/src/os/windows/io/socket.rs
+++ b/library/std/src/os/windows/io/socket.rs
@@ -336,3 +336,19 @@ impl From<OwnedSocket> for crate::net::UdpSocket {
         unsafe { Self::from_raw_socket(owned.into_raw_socket()) }
     }
 }
+
+#[unstable(feature = "unbound_socket", issue = "none")]
+impl AsSocket for crate::net::UnboundUdpSocket {
+    #[inline]
+    fn as_socket(&self) -> BorrowedSocket<'_> {
+        unsafe { BorrowedSocket::borrow_raw(self.as_raw_socket()) }
+    }
+}
+
+#[unstable(feature = "unbound_socket", issue = "none")]
+impl From<crate::net::UnboundUdpSocket> for OwnedSocket {
+    #[inline]
+    fn from(unbound_udp_socket: crate::net::UnboundUdpSocket) -> OwnedSocket {
+        unsafe { OwnedSocket::from_raw_socket(unbound_udp_socket.into_raw_socket()) }
+    }
+}
diff --git a/library/std/src/sys/unix/net.rs b/library/std/src/sys/unix/net.rs
index b84bf8f9264a4..ac4488820e05e 100644
--- a/library/std/src/sys/unix/net.rs
+++ b/library/std/src/sys/unix/net.rs
@@ -2,7 +2,7 @@ use crate::cmp;
 use crate::ffi::CStr;
 use crate::io::{self, IoSlice, IoSliceMut};
 use crate::mem;
-use crate::net::{Shutdown, SocketAddr};
+use crate::net::{Shutdown, SocketAddr, SocketAddrFamily};
 use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd};
 use crate::str;
 use crate::sys::fd::FileDesc;
@@ -59,10 +59,10 @@ pub fn cvt_gai(err: c_int) -> io::Result<()> {
 }
 
 impl Socket {
-    pub fn new(addr: &SocketAddr, ty: c_int) -> io::Result<Socket> {
-        let fam = match *addr {
-            SocketAddr::V4(..) => libc::AF_INET,
-            SocketAddr::V6(..) => libc::AF_INET6,
+    pub fn new(addr: SocketAddrFamily, ty: c_int) -> io::Result<Socket> {
+        let fam = match addr {
+            SocketAddrFamily::InetV4 => libc::AF_INET,
+            SocketAddrFamily::InetV6 => libc::AF_INET6,
         };
         Socket::new_raw(fam, ty)
     }
diff --git a/library/std/src/sys/windows/c.rs b/library/std/src/sys/windows/c.rs
index 917fc8e4995e6..dcc452842e097 100644
--- a/library/std/src/sys/windows/c.rs
+++ b/library/std/src/sys/windows/c.rs
@@ -226,6 +226,8 @@ pub const IP_TTL: c_int = 4;
 pub const IPV6_V6ONLY: c_int = 27;
 pub const SO_ERROR: c_int = 0x1007;
 pub const SO_BROADCAST: c_int = 0x0020;
+pub const SO_REUSEADDR: c_int = 0x0004;
+pub const SO_EXCLUSIVEADDRUSE: c_int = !SO_REUSEADDR;
 pub const IP_MULTICAST_LOOP: c_int = 11;
 pub const IPV6_MULTICAST_LOOP: c_int = 11;
 pub const IP_MULTICAST_TTL: c_int = 10;
diff --git a/library/std/src/sys/windows/net.rs b/library/std/src/sys/windows/net.rs
index e0701a498fad7..d36a4db0bc74f 100644
--- a/library/std/src/sys/windows/net.rs
+++ b/library/std/src/sys/windows/net.rs
@@ -3,7 +3,7 @@
 use crate::cmp;
 use crate::io::{self, IoSlice, IoSliceMut, Read};
 use crate::mem;
-use crate::net::{Shutdown, SocketAddr};
+use crate::net::{Shutdown, SocketAddr, SocketAddrFamily};
 use crate::os::windows::io::{
     AsRawSocket, AsSocket, BorrowedSocket, FromRawSocket, IntoRawSocket, OwnedSocket, RawSocket,
 };
@@ -100,10 +100,10 @@ where
 }
 
 impl Socket {
-    pub fn new(addr: &SocketAddr, ty: c_int) -> io::Result<Socket> {
-        let family = match *addr {
-            SocketAddr::V4(..) => c::AF_INET,
-            SocketAddr::V6(..) => c::AF_INET6,
+    pub fn new(addr_family: SocketAddrFamily, ty: c_int) -> io::Result<Socket> {
+        let family = match addr_family {
+            SocketAddrFamily::InetV4 => c::AF_INET,
+            SocketAddrFamily::InetV6 => c::AF_INET6,
         };
         let socket = unsafe {
             c::WSASocketW(
diff --git a/library/std/src/sys_common/net.rs b/library/std/src/sys_common/net.rs
index fad4a63331b59..0507f40ca8c71 100644
--- a/library/std/src/sys_common/net.rs
+++ b/library/std/src/sys_common/net.rs
@@ -6,7 +6,7 @@ use crate::convert::{TryFrom, TryInto};
 use crate::fmt;
 use crate::io::{self, ErrorKind, IoSlice, IoSliceMut};
 use crate::mem;
-use crate::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr};
+use crate::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr, SocketAddrFamily};
 use crate::ptr;
 use crate::sys::common::small_c_string::run_with_cstr;
 use crate::sys::net::netc as c;
@@ -221,22 +221,13 @@ pub struct TcpStream {
 impl TcpStream {
     pub fn connect(addr: io::Result<&SocketAddr>) -> io::Result<TcpStream> {
         let addr = addr?;
-
-        init();
-
-        let sock = Socket::new(addr, c::SOCK_STREAM)?;
-
-        let (addr, len) = addr.into_inner();
-        cvt_r(|| unsafe { c::connect(sock.as_raw(), addr.as_ptr(), len) })?;
-        Ok(TcpStream { inner: sock })
+        let unbound_sock = UnboundTcpSocket::new(addr.family())?;
+        unbound_sock.connect(addr)
     }
 
     pub fn connect_timeout(addr: &SocketAddr, timeout: Duration) -> io::Result<TcpStream> {
-        init();
-
-        let sock = Socket::new(addr, c::SOCK_STREAM)?;
-        sock.connect_timeout(addr, timeout)?;
-        Ok(TcpStream { inner: sock })
+        let unbound_sock = UnboundTcpSocket::new(addr.family())?;
+        unbound_sock.connect_timeout(addr, timeout)
     }
 
     pub fn socket(&self) -> &Socket {
@@ -387,40 +378,8 @@ pub struct TcpListener {
 impl TcpListener {
     pub fn bind(addr: io::Result<&SocketAddr>) -> io::Result<TcpListener> {
         let addr = addr?;
-
-        init();
-
-        let sock = Socket::new(addr, c::SOCK_STREAM)?;
-
-        // On platforms with Berkeley-derived sockets, this allows to quickly
-        // rebind a socket, without needing to wait for the OS to clean up the
-        // previous one.
-        //
-        // On Windows, this allows rebinding sockets which are actively in use,
-        // which allows “socket hijacking”, so we explicitly don't set it here.
-        // https://docs.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse
-        #[cfg(not(windows))]
-        setsockopt(&sock, c::SOL_SOCKET, c::SO_REUSEADDR, 1 as c_int)?;
-
-        // Bind our new socket
-        let (addr, len) = addr.into_inner();
-        cvt(unsafe { c::bind(sock.as_raw(), addr.as_ptr(), len as _) })?;
-
-        cfg_if::cfg_if! {
-            if #[cfg(target_os = "horizon")] {
-                // The 3DS doesn't support a big connection backlog. Sometimes
-                // it allows up to about 37, but other times it doesn't even
-                // accept 32. There may be a global limitation causing this.
-                let backlog = 20;
-            } else {
-                // The default for all other platforms
-                let backlog = 128;
-            }
-        }
-
-        // Start listening
-        cvt(unsafe { c::listen(sock.as_raw(), backlog) })?;
-        Ok(TcpListener { inner: sock })
+        let unbound_sock = UnboundTcpSocket::new(addr.family())?;
+        unbound_sock.listen(addr)
     }
 
     pub fn socket(&self) -> &Socket {
@@ -507,7 +466,7 @@ impl UdpSocket {
 
         init();
 
-        let sock = Socket::new(addr, c::SOCK_DGRAM)?;
+        let sock = Socket::new(addr.family(), c::SOCK_DGRAM)?;
         let (addr, len) = addr.into_inner();
         cvt(unsafe { c::bind(sock.as_raw(), addr.as_ptr(), len as _) })?;
         Ok(UdpSocket { inner: sock })
@@ -743,3 +702,148 @@ impl<'a> IntoInner<(SocketAddrCRepr, c::socklen_t)> for &'a SocketAddr {
         }
     }
 }
+
+/// Represents a UDP socket that is not bound to any SocketAddr yet.
+pub struct UnboundUdpSocket {
+    inner: Socket,
+    addr_family: SocketAddrFamily,
+}
+
+impl UnboundUdpSocket {
+    /// Creates a new UDP socket without binding.
+    pub fn new(addr_family: SocketAddrFamily) -> io::Result<Self> {
+        init();
+
+        let inner = Socket::new(addr_family, c::SOCK_DGRAM)?;
+        let new_self = Self { inner, addr_family };
+        Ok(new_self)
+    }
+
+    pub fn socket(&self) -> &Socket {
+        &self.inner
+    }
+
+    pub fn into_socket(self) -> Socket {
+        self.inner
+    }
+
+    pub fn set_reuseaddr(&self, enable: bool) -> io::Result<()> {
+        // The `SO_REUSEADDR` option behaves with subtle differences on different platforms.
+        // Here is a good summary:
+        // https://stackoverflow.com/a/14388707/1783732
+        setsockopt(&self.inner, c::SOL_SOCKET, c::SO_REUSEADDR, enable as c_int)
+    }
+
+    cfg_if::cfg_if! {
+        if #[cfg(windows)] {
+            /// Set "SO_EXCLUSIVEADDRUSE" to true or false on Windows.
+            pub fn set_exclusiveaddruse(&self, enable: bool) -> io::Result<()> {
+                // This is a sockopt in Windows to prevent 'port hijacking':
+                // https://docs.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse
+                setsockopt(&self.inner, c::SOL_SOCKET, c::SO_EXCLUSIVEADDRUSE, enable as c_int)
+            }
+        } else {
+            /// Returns an error on non-Windows platforms.
+            pub fn set_exclusiveaddruse(&self, _enable: bool) -> io::Result<()> {
+                Err(io::Error::from(io::ErrorKind::Unsupported))
+            }
+
+        }
+    }
+
+    cfg_if::cfg_if! {
+        if #[cfg(unix)] {
+            /// Sets `SO_REUSEPORT` option to true or false on Unix platforms, including
+            /// BSDs, macOS and Linux.
+            pub fn set_reuseport(&self, enable: bool) -> io::Result<()> {
+                setsockopt(&self.inner, c::SOL_SOCKET, c::SO_REUSEPORT, enable as c_int)
+            }
+        } else {
+            /// Returns an error on non-Unix platforms. Note, it is possible
+            /// that some non-Windows and non-Unix platforms also support this option.
+            pub fn set_reuseport(&self, _enable: bool) -> io::Result<()> {
+                Err(io::Error::from(io::ErrorKind::Unsupported))
+            }
+        }
+    }
+
+    /// Binds the socket to an address, and returns a `UdpSocket`.
+    pub fn bind(self, addr: &SocketAddr) -> io::Result<UdpSocket> {
+        if self.addr_family != addr.family() {
+            return Err(io::Error::from(io::ErrorKind::InvalidInput));
+        }
+
+        let (addr, len) = addr.into_inner();
+        cvt(unsafe { c::bind(self.inner.as_raw(), addr.as_ptr(), len as _) })?;
+        Ok(UdpSocket { inner: self.inner })
+    }
+}
+
+impl fmt::Debug for UnboundUdpSocket {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let name = if cfg!(windows) { "socket" } else { "fd" };
+        f.debug_struct("UnboundUdpSocket").field(name, &self.inner.as_raw()).finish()
+    }
+}
+
+/// Represents a TCP socket that is not bound to a `SocketAddr` yet.
+pub struct UnboundTcpSocket {
+    inner: Socket,
+}
+
+impl UnboundTcpSocket {
+    pub fn new(addr_family: SocketAddrFamily) -> io::Result<Self> {
+        init();
+
+        let sock = Socket::new(addr_family, c::SOCK_STREAM)?;
+        Ok(Self { inner: sock })
+    }
+
+    #[cfg(not(windows))]
+    pub fn set_reuseaddr(&self, enable: bool) -> io::Result<()> {
+        setsockopt(&self.inner, c::SOL_SOCKET, c::SO_REUSEADDR, enable as c_int)
+    }
+
+    pub fn connect(self, addr: &SocketAddr) -> io::Result<TcpStream> {
+        let (addr, len) = addr.into_inner();
+        cvt_r(|| unsafe { c::connect(self.inner.as_raw(), addr.as_ptr(), len) })?;
+        Ok(TcpStream { inner: self.inner })
+    }
+
+    pub fn connect_timeout(self, addr: &SocketAddr, timeout: Duration) -> io::Result<TcpStream> {
+        self.inner.connect_timeout(addr, timeout)?;
+        Ok(TcpStream { inner: self.inner })
+    }
+
+    pub fn listen(self, addr: &SocketAddr) -> io::Result<TcpListener> {
+        // On platforms with Berkeley-derived sockets, this allows to quickly
+        // rebind a socket, without needing to wait for the OS to clean up the
+        // previous one.
+        //
+        // On Windows, this allows rebinding sockets which are actively in use,
+        // which allows “socket hijacking”, so we explicitly don't set it here.
+        // https://docs.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse
+        #[cfg(not(windows))]
+        self.set_reuseaddr(true)?;
+
+        // Bind our socket
+        let (addr, len) = addr.into_inner();
+        cvt(unsafe { c::bind(self.inner.as_raw(), addr.as_ptr(), len as _) })?;
+
+        cfg_if::cfg_if! {
+            if #[cfg(target_os = "horizon")] {
+                // The 3DS doesn't support a big connection backlog. Sometimes
+                // it allows up to about 37, but other times it doesn't even
+                // accept 32. There may be a global limitation causing this.
+                let backlog = 20;
+            } else {
+                // The default for all other platforms
+                let backlog = 128;
+            }
+        }
+
+        // Start listening
+        cvt(unsafe { c::listen(self.inner.as_raw(), backlog) })?;
+        Ok(TcpListener { inner: self.inner })
+    }
+}