about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2021-08-20 11:00:55 +0000
committerbors <bors@rust-lang.org>2021-08-20 11:00:55 +0000
commit521734787ecf80ff12df7ca5998f7ec0b3b7b2c9 (patch)
tree14d6353b5d2990f5572b35cfbf0c13422f0fd6f0
parent9ccf661694423895b02e513c69e6ad263b2f3d8e (diff)
parentb4dfa198bf4879bc159c7119bd4188ccd625f71d (diff)
downloadrust-521734787ecf80ff12df7ca5998f7ec0b3b7b2c9.tar.gz
rust-521734787ecf80ff12df7ca5998f7ec0b3b7b2c9.zip
Auto merge of #87329 - sunfishcode:sunfishcode/io-safety, r=joshtriplett
I/O safety.

Introduce `OwnedFd` and `BorrowedFd`, and the `AsFd` trait, and
implementations of `AsFd`, `From<OwnedFd>` and `From<T> for OwnedFd`
for relevant types, along with Windows counterparts for handles and
sockets.

Tracking issue: <https://github.com/rust-lang/rust/issues/87074>

RFC: <https://github.com/rust-lang/rfcs/blob/master/text/3128-io-safety.md>

Highlights:
 - The doc comments at the top of library/std/src/os/unix/io/mod.rs and library/std/src/os/windows/io/mod.rs
 - The new types and traits in library/std/src/os/unix/io/fd.rs and library/std/src/os/windows/io/handle.rs
 - The removal of the `RawHandle` struct the Windows impl, which had the same name as the `RawHandle` type alias, and its functionality is now folded into `Handle`.

Managing five levels of wrapping (File wraps sys::fs::File wraps sys::fs::FileDesc wraps OwnedFd wraps RawFd, etc.) made for a fair amount of churn and verbose as/into/from sequences in some places. I've managed to simplify some of them, but I'm open to ideas here.

r? `@joshtriplett`
-rw-r--r--library/std/src/fs.rs6
-rw-r--r--library/std/src/net/tcp.rs12
-rw-r--r--library/std/src/net/udp.rs6
-rw-r--r--library/std/src/net/udp/tests.rs3
-rw-r--r--library/std/src/os/fd/mod.rs13
-rw-r--r--library/std/src/os/fd/net.rs (renamed from library/std/src/os/unix/net/raw_fd.rs)13
-rw-r--r--library/std/src/os/fd/owned.rs289
-rw-r--r--library/std/src/os/fd/raw.rs (renamed from library/std/src/os/unix/io.rs)37
-rw-r--r--library/std/src/os/linux/process.rs26
-rw-r--r--library/std/src/os/mod.rs3
-rw-r--r--library/std/src/os/unix/io/fd.rs9
-rw-r--r--library/std/src/os/unix/io/fd/tests.rs11
-rw-r--r--library/std/src/os/unix/io/mod.rs57
-rw-r--r--library/std/src/os/unix/io/raw.rs5
-rw-r--r--library/std/src/os/unix/mod.rs2
-rw-r--r--library/std/src/os/unix/net/datagram.rs44
-rw-r--r--library/std/src/os/unix/net/listener.rs14
-rw-r--r--library/std/src/os/unix/net/mod.rs3
-rw-r--r--library/std/src/os/unix/net/stream.rs40
-rw-r--r--library/std/src/os/unix/process.rs74
-rw-r--r--library/std/src/os/wasi/fs.rs30
-rw-r--r--library/std/src/os/wasi/io.rs208
-rw-r--r--library/std/src/os/wasi/io/fd.rs9
-rw-r--r--library/std/src/os/wasi/io/fd/tests.rs11
-rw-r--r--library/std/src/os/wasi/io/mod.rs12
-rw-r--r--library/std/src/os/wasi/io/raw.rs5
-rw-r--r--library/std/src/os/wasi/mod.rs3
-rw-r--r--library/std/src/os/wasi/net/mod.rs3
-rw-r--r--library/std/src/os/windows/io/handle.rs386
-rw-r--r--library/std/src/os/windows/io/mod.rs56
-rw-r--r--library/std/src/os/windows/io/raw.rs (renamed from library/std/src/os/windows/io.rs)27
-rw-r--r--library/std/src/os/windows/io/socket.rs216
-rw-r--r--library/std/src/os/windows/mod.rs7
-rw-r--r--library/std/src/os/windows/process.rs46
-rw-r--r--library/std/src/os/windows/thread.rs4
-rw-r--r--library/std/src/process.rs18
-rw-r--r--library/std/src/sys/unix/fd.rs115
-rw-r--r--library/std/src/sys/unix/fd/tests.rs3
-rw-r--r--library/std/src/sys/unix/fs.rs77
-rw-r--r--library/std/src/sys/unix/net.rs92
-rw-r--r--library/std/src/sys/unix/pipe.rs63
-rw-r--r--library/std/src/sys/unix/process/process_common.rs15
-rw-r--r--library/std/src/sys/unix/process/process_unix.rs16
-rw-r--r--library/std/src/sys/unix/stdio.rs65
-rw-r--r--library/std/src/sys/wasi/fd.rs163
-rw-r--r--library/std/src/sys/wasi/fs.rs49
-rw-r--r--library/std/src/sys/wasi/net.rs126
-rw-r--r--library/std/src/sys/wasi/stdio.rs19
-rw-r--r--library/std/src/sys/windows/fs.rs73
-rw-r--r--library/std/src/sys/windows/handle.rs122
-rw-r--r--library/std/src/sys/windows/net.rs106
-rw-r--r--library/std/src/sys/windows/os.rs2
-rw-r--r--library/std/src/sys/windows/pipe.rs17
-rw-r--r--library/std/src/sys/windows/process.rs41
-rw-r--r--library/std/src/sys/windows/stdio.rs21
-rw-r--r--library/std/src/sys/windows/thread.rs5
-rw-r--r--library/std/src/sys_common/net.rs42
57 files changed, 2229 insertions, 711 deletions
diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs
index 2c04481c04e..bdb172907ff 100644
--- a/library/std/src/fs.rs
+++ b/library/std/src/fs.rs
@@ -587,6 +587,12 @@ impl File {
     }
 }
 
+// In addition to the `impl`s here, `File` also has `impl`s for
+// `AsFd`/`From<OwnedFd>`/`Into<OwnedFd>` and
+// `AsRawFd`/`IntoRawFd`/`FromRawFd`, on Unix and WASI, and
+// `AsHandle`/`From<OwnedHandle>`/`Into<OwnedHandle>` and
+// `AsRawHandle`/`IntoRawHandle`/`FromRawHandle` on Windows.
+
 impl AsInner<fs_imp::File> for File {
     fn as_inner(&self) -> &fs_imp::File {
         &self.inner
diff --git a/library/std/src/net/tcp.rs b/library/std/src/net/tcp.rs
index c8eb4811f7e..336891ec1eb 100644
--- a/library/std/src/net/tcp.rs
+++ b/library/std/src/net/tcp.rs
@@ -546,6 +546,12 @@ impl TcpStream {
     }
 }
 
+// In addition to the `impl`s here, `TcpStream` also has `impl`s for
+// `AsFd`/`From<OwnedFd>`/`Into<OwnedFd>` and
+// `AsRawFd`/`IntoRawFd`/`FromRawFd`, on Unix and WASI, and
+// `AsSocket`/`From<OwnedSocket>`/`Into<OwnedSocket>` and
+// `AsRawSocket`/`IntoRawSocket`/`FromRawSocket` on Windows.
+
 #[stable(feature = "rust1", since = "1.0.0")]
 impl Read for TcpStream {
     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
@@ -915,6 +921,12 @@ impl TcpListener {
     }
 }
 
+// In addition to the `impl`s here, `TcpListener` also has `impl`s for
+// `AsFd`/`From<OwnedFd>`/`Into<OwnedFd>` and
+// `AsRawFd`/`IntoRawFd`/`FromRawFd`, on Unix and WASI, and
+// `AsSocket`/`From<OwnedSocket>`/`Into<OwnedSocket>` and
+// `AsRawSocket`/`IntoRawSocket`/`FromRawSocket` on Windows.
+
 #[stable(feature = "rust1", since = "1.0.0")]
 impl<'a> Iterator for Incoming<'a> {
     type Item = io::Result<TcpStream>;
diff --git a/library/std/src/net/udp.rs b/library/std/src/net/udp.rs
index d2088a12b2c..871505843af 100644
--- a/library/std/src/net/udp.rs
+++ b/library/std/src/net/udp.rs
@@ -779,6 +779,12 @@ impl UdpSocket {
     }
 }
 
+// In addition to the `impl`s here, `UdpSocket` also has `impl`s for
+// `AsFd`/`From<OwnedFd>`/`Into<OwnedFd>` and
+// `AsRawFd`/`IntoRawFd`/`FromRawFd`, on Unix and WASI, and
+// `AsSocket`/`From<OwnedSocket>`/`Into<OwnedSocket>` and
+// `AsRawSocket`/`IntoRawSocket`/`FromRawSocket` on Windows.
+
 impl AsInner<net_imp::UdpSocket> for UdpSocket {
     fn as_inner(&self) -> &net_imp::UdpSocket {
         &self.0
diff --git a/library/std/src/net/udp/tests.rs b/library/std/src/net/udp/tests.rs
index fbed3d32d45..a51113dd9e7 100644
--- a/library/std/src/net/udp/tests.rs
+++ b/library/std/src/net/udp/tests.rs
@@ -2,7 +2,6 @@ use crate::io::ErrorKind;
 use crate::net::test::{next_test_ip4, next_test_ip6};
 use crate::net::*;
 use crate::sync::mpsc::channel;
-use crate::sys_common::AsInner;
 use crate::thread;
 use crate::time::{Duration, Instant};
 
@@ -173,7 +172,7 @@ fn debug() {
     let socket_addr = next_test_ip4();
 
     let udpsock = t!(UdpSocket::bind(&socket_addr));
-    let udpsock_inner = udpsock.0.socket().as_inner();
+    let udpsock_inner = udpsock.0.socket().as_raw();
     let compare = format!("UdpSocket {{ addr: {:?}, {}: {:?} }}", socket_addr, name, udpsock_inner);
     assert_eq!(format!("{:?}", udpsock), compare);
 }
diff --git a/library/std/src/os/fd/mod.rs b/library/std/src/os/fd/mod.rs
new file mode 100644
index 00000000000..df11dc21aa7
--- /dev/null
+++ b/library/std/src/os/fd/mod.rs
@@ -0,0 +1,13 @@
+//! Owned and borrowed Unix-like file descriptors.
+
+#![unstable(feature = "io_safety", issue = "87074")]
+#![deny(unsafe_op_in_unsafe_fn)]
+
+// `RawFd`, `AsRawFd`, etc.
+pub mod raw;
+
+// `OwnedFd`, `AsFd`, etc.
+pub mod owned;
+
+// Implementations for `AsRawFd` etc. for network types.
+mod net;
diff --git a/library/std/src/os/unix/net/raw_fd.rs b/library/std/src/os/fd/net.rs
index b3f12844101..843f45f7f5f 100644
--- a/library/std/src/os/unix/net/raw_fd.rs
+++ b/library/std/src/os/fd/net.rs
@@ -1,4 +1,5 @@
-use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
+use crate::os::fd::owned::OwnedFd;
+use crate::os::fd::raw::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
 use crate::sys_common::{self, AsInner, FromInner, IntoInner};
 use crate::{net, sys};
 
@@ -8,7 +9,7 @@ macro_rules! impl_as_raw_fd {
         impl AsRawFd for net::$t {
             #[inline]
             fn as_raw_fd(&self) -> RawFd {
-                *self.as_inner().socket().as_inner()
+                self.as_inner().socket().as_raw_fd()
             }
         }
     )*};
@@ -21,8 +22,10 @@ macro_rules! impl_from_raw_fd {
         impl FromRawFd for net::$t {
             #[inline]
             unsafe fn from_raw_fd(fd: RawFd) -> net::$t {
-                let socket = sys::net::Socket::from_inner(fd);
-                net::$t::from_inner(sys_common::net::$t::from_inner(socket))
+                unsafe {
+                    let socket = sys::net::Socket::from_inner(FromInner::from_inner(OwnedFd::from_raw_fd(fd)));
+                    net::$t::from_inner(sys_common::net::$t::from_inner(socket))
+                }
             }
         }
     )*};
@@ -35,7 +38,7 @@ macro_rules! impl_into_raw_fd {
         impl IntoRawFd for net::$t {
             #[inline]
             fn into_raw_fd(self) -> RawFd {
-                self.into_inner().into_socket().into_inner()
+                self.into_inner().into_socket().into_inner().into_inner().into_raw_fd()
             }
         }
     )*};
diff --git a/library/std/src/os/fd/owned.rs b/library/std/src/os/fd/owned.rs
new file mode 100644
index 00000000000..52d7d4690d3
--- /dev/null
+++ b/library/std/src/os/fd/owned.rs
@@ -0,0 +1,289 @@
+//! Owned and borrowed Unix-like file descriptors.
+
+#![unstable(feature = "io_safety", issue = "87074")]
+#![deny(unsafe_op_in_unsafe_fn)]
+
+use super::raw::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
+use crate::fmt;
+use crate::fs;
+use crate::marker::PhantomData;
+use crate::mem::forget;
+use crate::sys_common::{AsInner, FromInner, IntoInner};
+
+/// A borrowed file descriptor.
+///
+/// This has a lifetime parameter to tie it to the lifetime of something that
+/// owns the file descriptor.
+///
+/// This uses `repr(transparent)` and has the representation of a host file
+/// descriptor, so it can be used in FFI in places where a file descriptor is
+/// passed as an argument, it is not captured or consumed, and it never has the
+/// value `-1`.
+#[derive(Copy, Clone)]
+#[repr(transparent)]
+#[rustc_layout_scalar_valid_range_start(0)]
+// libstd/os/raw/mod.rs assures me that every libstd-supported platform has a
+// 32-bit c_int. Below is -2, in two's complement, but that only works out
+// because c_int is 32 bits.
+#[rustc_layout_scalar_valid_range_end(0xFF_FF_FF_FE)]
+#[unstable(feature = "io_safety", issue = "87074")]
+pub struct BorrowedFd<'fd> {
+    fd: RawFd,
+    _phantom: PhantomData<&'fd OwnedFd>,
+}
+
+/// An owned file descriptor.
+///
+/// This closes the file descriptor on drop.
+///
+/// This uses `repr(transparent)` and has the representation of a host file
+/// descriptor, so it can be used in FFI in places where a file descriptor is
+/// passed as a consumed argument or returned as an owned value, and it never
+/// has the value `-1`.
+#[repr(transparent)]
+#[rustc_layout_scalar_valid_range_start(0)]
+// libstd/os/raw/mod.rs assures me that every libstd-supported platform has a
+// 32-bit c_int. Below is -2, in two's complement, but that only works out
+// because c_int is 32 bits.
+#[rustc_layout_scalar_valid_range_end(0xFF_FF_FF_FE)]
+#[unstable(feature = "io_safety", issue = "87074")]
+pub struct OwnedFd {
+    fd: RawFd,
+}
+
+impl BorrowedFd<'_> {
+    /// Return a `BorrowedFd` holding the given raw file descriptor.
+    ///
+    /// # Safety
+    ///
+    /// The resource pointed to by `fd` must remain open for the duration of
+    /// the returned `BorrowedFd`, and it must not have the value `-1`.
+    #[inline]
+    #[unstable(feature = "io_safety", issue = "87074")]
+    pub unsafe fn borrow_raw_fd(fd: RawFd) -> Self {
+        assert_ne!(fd, u32::MAX as RawFd);
+        // SAFETY: we just asserted that the value is in the valid range and isn't `-1` (the only value bigger than `0xFF_FF_FF_FE` unsigned)
+        unsafe { Self { fd, _phantom: PhantomData } }
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl AsRawFd for BorrowedFd<'_> {
+    #[inline]
+    fn as_raw_fd(&self) -> RawFd {
+        self.fd
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl AsRawFd for OwnedFd {
+    #[inline]
+    fn as_raw_fd(&self) -> RawFd {
+        self.fd
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl IntoRawFd for OwnedFd {
+    #[inline]
+    fn into_raw_fd(self) -> RawFd {
+        let fd = self.fd;
+        forget(self);
+        fd
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl FromRawFd for OwnedFd {
+    /// Constructs a new instance of `Self` from the given raw file descriptor.
+    ///
+    /// # Safety
+    ///
+    /// The resource pointed to by `fd` must be open and suitable for assuming
+    /// ownership. The resource must not require any cleanup other than `close`.
+    #[inline]
+    unsafe fn from_raw_fd(fd: RawFd) -> Self {
+        assert_ne!(fd, u32::MAX as RawFd);
+        // SAFETY: we just asserted that the value is in the valid range and isn't `-1` (the only value bigger than `0xFF_FF_FF_FE` unsigned)
+        unsafe { Self { fd } }
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl Drop for OwnedFd {
+    #[inline]
+    fn drop(&mut self) {
+        unsafe {
+            // Note that errors are ignored when closing a file descriptor. The
+            // reason for this is that if an error occurs we don't actually know if
+            // the file descriptor was closed or not, and if we retried (for
+            // something like EINTR), we might close another valid file descriptor
+            // opened after we closed ours.
+            let _ = libc::close(self.fd);
+        }
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl fmt::Debug for BorrowedFd<'_> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("BorrowedFd").field("fd", &self.fd).finish()
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl fmt::Debug for OwnedFd {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("OwnedFd").field("fd", &self.fd).finish()
+    }
+}
+
+/// A trait to borrow the file descriptor from an underlying object.
+///
+/// This is only available on unix platforms and must be imported in order to
+/// call the method. Windows platforms have a corresponding `AsHandle` and
+/// `AsSocket` set of traits.
+#[unstable(feature = "io_safety", issue = "87074")]
+pub trait AsFd {
+    /// Borrows the file descriptor.
+    ///
+    /// # Example
+    ///
+    /// ```rust,no_run
+    /// # #![feature(io_safety)]
+    /// use std::fs::File;
+    /// # use std::io;
+    /// # #[cfg(target_os = "wasi")]
+    /// # use std::os::wasi::io::{AsFd, BorrowedFd};
+    /// # #[cfg(unix)]
+    /// # use std::os::unix::io::{AsFd, BorrowedFd};
+    ///
+    /// let mut f = File::open("foo.txt")?;
+    /// # #[cfg(any(unix, target_os = "wasi"))]
+    /// let borrowed_fd: BorrowedFd<'_> = f.as_fd();
+    /// # Ok::<(), io::Error>(())
+    /// ```
+    #[unstable(feature = "io_safety", issue = "87074")]
+    fn as_fd(&self) -> BorrowedFd<'_>;
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl AsFd for BorrowedFd<'_> {
+    #[inline]
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        *self
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl AsFd for OwnedFd {
+    #[inline]
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        // Safety: `OwnedFd` and `BorrowedFd` have the same validity
+        // invariants, and the `BorrowdFd` is bounded by the lifetime
+        // of `&self`.
+        unsafe { BorrowedFd::borrow_raw_fd(self.as_raw_fd()) }
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl AsFd for fs::File {
+    #[inline]
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.as_inner().as_fd()
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl From<fs::File> for OwnedFd {
+    #[inline]
+    fn from(file: fs::File) -> OwnedFd {
+        file.into_inner().into_inner().into_inner()
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl From<OwnedFd> for fs::File {
+    #[inline]
+    fn from(owned_fd: OwnedFd) -> Self {
+        Self::from_inner(FromInner::from_inner(FromInner::from_inner(owned_fd)))
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl AsFd for crate::net::TcpStream {
+    #[inline]
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.as_inner().socket().as_fd()
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl From<crate::net::TcpStream> for OwnedFd {
+    #[inline]
+    fn from(tcp_stream: crate::net::TcpStream) -> OwnedFd {
+        tcp_stream.into_inner().into_socket().into_inner().into_inner().into()
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl From<OwnedFd> for crate::net::TcpStream {
+    #[inline]
+    fn from(owned_fd: OwnedFd) -> Self {
+        Self::from_inner(FromInner::from_inner(FromInner::from_inner(FromInner::from_inner(
+            owned_fd,
+        ))))
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl AsFd for crate::net::TcpListener {
+    #[inline]
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.as_inner().socket().as_fd()
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl From<crate::net::TcpListener> for OwnedFd {
+    #[inline]
+    fn from(tcp_listener: crate::net::TcpListener) -> OwnedFd {
+        tcp_listener.into_inner().into_socket().into_inner().into_inner().into()
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl From<OwnedFd> for crate::net::TcpListener {
+    #[inline]
+    fn from(owned_fd: OwnedFd) -> Self {
+        Self::from_inner(FromInner::from_inner(FromInner::from_inner(FromInner::from_inner(
+            owned_fd,
+        ))))
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl AsFd for crate::net::UdpSocket {
+    #[inline]
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.as_inner().socket().as_fd()
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl From<crate::net::UdpSocket> for OwnedFd {
+    #[inline]
+    fn from(udp_socket: crate::net::UdpSocket) -> OwnedFd {
+        udp_socket.into_inner().into_socket().into_inner().into_inner().into()
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl From<OwnedFd> for crate::net::UdpSocket {
+    #[inline]
+    fn from(owned_fd: OwnedFd) -> Self {
+        Self::from_inner(FromInner::from_inner(FromInner::from_inner(FromInner::from_inner(
+            owned_fd,
+        ))))
+    }
+}
diff --git a/library/std/src/os/unix/io.rs b/library/std/src/os/fd/raw.rs
index 07c30bfa9ed..f874cf0b42d 100644
--- a/library/std/src/os/unix/io.rs
+++ b/library/std/src/os/fd/raw.rs
@@ -1,23 +1,25 @@
-//! Unix-specific extensions to general I/O primitives.
+//! Raw Unix-like file descriptors.
 
 #![stable(feature = "rust1", since = "1.0.0")]
 
 use crate::fs;
 use crate::io;
 use crate::os::raw;
-use crate::sys;
-use crate::sys_common::{AsInner, FromInner, IntoInner};
+#[cfg(unix)]
+use crate::os::unix::io::OwnedFd;
+#[cfg(target_os = "wasi")]
+use crate::os::wasi::io::OwnedFd;
+use crate::sys_common::{AsInner, IntoInner};
 
 /// Raw file descriptors.
 #[stable(feature = "rust1", since = "1.0.0")]
 pub type RawFd = raw::c_int;
 
-/// A trait to extract the raw unix file descriptor from an underlying
-/// object.
+/// A trait to extract the raw file descriptor from an underlying object.
 ///
-/// This is only available on unix platforms and must be imported in order
-/// to call the method. Windows platforms have a corresponding `AsRawHandle`
-/// and `AsRawSocket` set of traits.
+/// This is only available on unix and WASI platforms and must be imported in
+/// order to call the method. Windows platforms have a corresponding
+/// `AsRawHandle` and `AsRawSocket` set of traits.
 #[stable(feature = "rust1", since = "1.0.0")]
 pub trait AsRawFd {
     /// Extracts the raw file descriptor.
@@ -31,10 +33,14 @@ pub trait AsRawFd {
     /// ```no_run
     /// use std::fs::File;
     /// # use std::io;
+    /// #[cfg(unix)]
     /// use std::os::unix::io::{AsRawFd, RawFd};
+    /// #[cfg(target_os = "wasi")]
+    /// use std::os::wasi::io::{AsRawFd, RawFd};
     ///
     /// let mut f = File::open("foo.txt")?;
     /// // Note that `raw_fd` is only valid as long as `f` exists.
+    /// #[cfg(any(unix, target_os = "wasi"))]
     /// let raw_fd: RawFd = f.as_raw_fd();
     /// # Ok::<(), io::Error>(())
     /// ```
@@ -64,12 +70,17 @@ pub trait FromRawFd {
     /// ```no_run
     /// use std::fs::File;
     /// # use std::io;
+    /// #[cfg(unix)]
     /// use std::os::unix::io::{FromRawFd, IntoRawFd, RawFd};
+    /// #[cfg(target_os = "wasi")]
+    /// use std::os::wasi::io::{FromRawFd, IntoRawFd, RawFd};
     ///
     /// let f = File::open("foo.txt")?;
+    /// # #[cfg(any(unix, target_os = "wasi"))]
     /// let raw_fd: RawFd = f.into_raw_fd();
     /// // SAFETY: no other functions should call `from_raw_fd`, so there
     /// // is only one owner for the file descriptor.
+    /// # #[cfg(any(unix, target_os = "wasi"))]
     /// let f = unsafe { File::from_raw_fd(raw_fd) };
     /// # Ok::<(), io::Error>(())
     /// ```
@@ -92,9 +103,13 @@ pub trait IntoRawFd {
     /// ```no_run
     /// use std::fs::File;
     /// # use std::io;
+    /// #[cfg(unix)]
     /// use std::os::unix::io::{IntoRawFd, RawFd};
+    /// #[cfg(target_os = "wasi")]
+    /// use std::os::wasi::io::{IntoRawFd, RawFd};
     ///
     /// let f = File::open("foo.txt")?;
+    /// #[cfg(any(unix, target_os = "wasi"))]
     /// let raw_fd: RawFd = f.into_raw_fd();
     /// # Ok::<(), io::Error>(())
     /// ```
@@ -128,21 +143,21 @@ impl FromRawFd for RawFd {
 impl AsRawFd for fs::File {
     #[inline]
     fn as_raw_fd(&self) -> RawFd {
-        self.as_inner().fd().raw()
+        self.as_inner().as_raw_fd()
     }
 }
 #[stable(feature = "from_raw_os", since = "1.1.0")]
 impl FromRawFd for fs::File {
     #[inline]
     unsafe fn from_raw_fd(fd: RawFd) -> fs::File {
-        fs::File::from_inner(sys::fs::File::from_inner(fd))
+        unsafe { fs::File::from(OwnedFd::from_raw_fd(fd)) }
     }
 }
 #[stable(feature = "into_raw_os", since = "1.4.0")]
 impl IntoRawFd for fs::File {
     #[inline]
     fn into_raw_fd(self) -> RawFd {
-        self.into_inner().into_fd().into_raw()
+        self.into_inner().into_inner().into_raw_fd()
     }
 }
 
diff --git a/library/std/src/os/linux/process.rs b/library/std/src/os/linux/process.rs
index 6daff0f003c..e3e7143c851 100644
--- a/library/std/src/os/linux/process.rs
+++ b/library/std/src/os/linux/process.rs
@@ -3,7 +3,7 @@
 #![unstable(feature = "linux_pidfd", issue = "82971")]
 
 use crate::io::Result;
-use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
+use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
 use crate::process;
 use crate::sealed::Sealed;
 #[cfg(not(doc))]
@@ -69,19 +69,37 @@ impl IntoInner<FileDesc> for PidFd {
 
 impl AsRawFd for PidFd {
     fn as_raw_fd(&self) -> RawFd {
-        self.as_inner().raw()
+        self.as_inner().as_raw_fd()
     }
 }
 
 impl FromRawFd for PidFd {
     unsafe fn from_raw_fd(fd: RawFd) -> Self {
-        Self::from_inner(FileDesc::new(fd))
+        Self::from_inner(FileDesc::from_raw_fd(fd))
     }
 }
 
 impl IntoRawFd for PidFd {
     fn into_raw_fd(self) -> RawFd {
-        self.into_inner().into_raw()
+        self.into_inner().into_raw_fd()
+    }
+}
+
+impl AsFd for PidFd {
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.as_inner().as_fd()
+    }
+}
+
+impl From<OwnedFd> for PidFd {
+    fn from(fd: OwnedFd) -> Self {
+        Self::from_inner(FileDesc::from_inner(fd))
+    }
+}
+
+impl From<PidFd> for OwnedFd {
+    fn from(pid_fd: PidFd) -> Self {
+        pid_fd.into_inner().into_inner()
     }
 }
 
diff --git a/library/std/src/os/mod.rs b/library/std/src/os/mod.rs
index 4c9814919cd..79e69673007 100644
--- a/library/std/src/os/mod.rs
+++ b/library/std/src/os/mod.rs
@@ -121,3 +121,6 @@ mod imp {
 #[cfg(not(doc))]
 #[stable(feature = "os", since = "1.0.0")]
 pub use imp::*;
+
+#[cfg(any(unix, target_os = "wasi", doc))]
+mod fd;
diff --git a/library/std/src/os/unix/io/fd.rs b/library/std/src/os/unix/io/fd.rs
new file mode 100644
index 00000000000..7795db7abc0
--- /dev/null
+++ b/library/std/src/os/unix/io/fd.rs
@@ -0,0 +1,9 @@
+//! Owned and borrowed file descriptors.
+
+#![unstable(feature = "io_safety", issue = "87074")]
+
+// Tests for this module
+#[cfg(test)]
+mod tests;
+
+pub use crate::os::fd::owned::*;
diff --git a/library/std/src/os/unix/io/fd/tests.rs b/library/std/src/os/unix/io/fd/tests.rs
new file mode 100644
index 00000000000..84d2a7a1a91
--- /dev/null
+++ b/library/std/src/os/unix/io/fd/tests.rs
@@ -0,0 +1,11 @@
+use crate::mem::size_of;
+use crate::os::unix::io::RawFd;
+
+#[test]
+fn test_raw_fd_layout() {
+    // `OwnedFd` and `BorrowedFd` use `rustc_layout_scalar_valid_range_start`
+    // and `rustc_layout_scalar_valid_range_end`, with values that depend on
+    // the bit width of `RawFd`. If this ever changes, those values will need
+    // to be updated.
+    assert_eq!(size_of::<RawFd>(), 4);
+}
diff --git a/library/std/src/os/unix/io/mod.rs b/library/std/src/os/unix/io/mod.rs
new file mode 100644
index 00000000000..0fd9591b016
--- /dev/null
+++ b/library/std/src/os/unix/io/mod.rs
@@ -0,0 +1,57 @@
+//! Unix-specific extensions to general I/O primitives.
+//!
+//! Just like raw pointers, raw file descriptors point to resources with
+//! dynamic lifetimes, and they can dangle if they outlive their resources
+//! or be forged if they're created from invalid values.
+//!
+//! This module provides three types for representing file descriptors,
+//! with different ownership properties: raw, borrowed, and owned, which are
+//! analogous to types used for representing pointers:
+//!
+//! | Type               | Analogous to |
+//! | ------------------ | ------------ |
+//! | [`RawFd`]          | `*const _`   |
+//! | [`BorrowedFd<'a>`] | `&'a _`      |
+//! | [`OwnedFd`]        | `Box<_>`     |
+//!
+//! Like raw pointers, `RawFd` values are primitive values. And in new code,
+//! they should be considered unsafe to do I/O on (analogous to dereferencing
+//! them). Rust did not always provide this guidance, so existing code in the
+//! Rust ecosystem often doesn't mark `RawFd` usage as unsafe. Once the
+//! `io_safety` feature is stable, libraries will be encouraged to migrate,
+//! either by adding `unsafe` to APIs that dereference `RawFd` values, or by
+//! using to `BorrowedFd` or `OwnedFd` instead.
+//!
+//! Like references, `BorrowedFd` values are tied to a lifetime, to ensure
+//! that they don't outlive the resource they point to. These are safe to
+//! use. `BorrowedFd` values may be used in APIs which provide safe access to
+//! any system call except for:
+//!  - `close`, because that would end the dynamic lifetime of the resource
+//!    without ending the lifetime of the file descriptor.
+//!  - `dup2`/`dup3`, in the second argument, because this argument is
+//!    closed and assigned a new resource, which may break the assumptions
+//!    other code using that file descriptor.
+//! This list doesn't include `mmap`, since `mmap` does do a proper borrow of
+//! its file descriptor argument. That said, `mmap` is unsafe for other
+//! reasons: it operates on raw pointers, and it can have undefined behavior if
+//! the underlying storage is mutated. Mutations may come from other processes,
+//! or from the same process if the API provides `BorrowedFd` access, since as
+//! mentioned earlier, `BorrowedFd` values may be used in APIs which provide
+//! safe access to any system call. Consequently, code using `mmap` and
+//! presenting a safe API must take full responsibility for ensuring that safe
+//! Rust code cannot evoke undefined behavior through it.
+//!
+//! Like boxes, `OwnedFd` values conceptually own the resource they point to,
+//! and free (close) it when they are dropped.
+//!
+//! [`BorrowedFd<'a>`]: crate::os::unix::io::BorrowedFd
+
+#![stable(feature = "rust1", since = "1.0.0")]
+
+mod fd;
+mod raw;
+
+#[unstable(feature = "io_safety", issue = "87074")]
+pub use fd::*;
+#[stable(feature = "rust1", since = "1.0.0")]
+pub use raw::*;
diff --git a/library/std/src/os/unix/io/raw.rs b/library/std/src/os/unix/io/raw.rs
new file mode 100644
index 00000000000..6317e317471
--- /dev/null
+++ b/library/std/src/os/unix/io/raw.rs
@@ -0,0 +1,5 @@
+//! Unix-specific extensions to general I/O primitives.
+
+#![stable(feature = "rust1", since = "1.0.0")]
+
+pub use crate::os::fd::raw::*;
diff --git a/library/std/src/os/unix/mod.rs b/library/std/src/os/unix/mod.rs
index 6c73d4b21dd..17a02595724 100644
--- a/library/std/src/os/unix/mod.rs
+++ b/library/std/src/os/unix/mod.rs
@@ -108,7 +108,7 @@ pub mod prelude {
     pub use super::fs::{FileTypeExt, MetadataExt, OpenOptionsExt, PermissionsExt};
     #[doc(no_inline)]
     #[stable(feature = "rust1", since = "1.0.0")]
-    pub use super::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
+    pub use super::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
     #[doc(no_inline)]
     #[stable(feature = "rust1", since = "1.0.0")]
     pub use super::process::{CommandExt, ExitStatusExt};
diff --git a/library/std/src/os/unix/net/datagram.rs b/library/std/src/os/unix/net/datagram.rs
index 9e39f70f68e..f11eec18cc5 100644
--- a/library/std/src/os/unix/net/datagram.rs
+++ b/library/std/src/os/unix/net/datagram.rs
@@ -21,7 +21,7 @@ use super::{sockaddr_un, SocketAddr};
 ))]
 use crate::io::{IoSlice, IoSliceMut};
 use crate::net::Shutdown;
-use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
+use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
 use crate::path::Path;
 use crate::sys::cvt;
 use crate::sys::net::Socket;
@@ -106,7 +106,7 @@ impl UnixDatagram {
             let socket = UnixDatagram::unbound()?;
             let (addr, len) = sockaddr_un(path.as_ref())?;
 
-            cvt(libc::bind(*socket.0.as_inner(), &addr as *const _ as *const _, len as _))?;
+            cvt(libc::bind(socket.as_raw_fd(), &addr as *const _ as *const _, len as _))?;
 
             Ok(socket)
         }
@@ -187,7 +187,7 @@ impl UnixDatagram {
         unsafe {
             let (addr, len) = sockaddr_un(path.as_ref())?;
 
-            cvt(libc::connect(*self.0.as_inner(), &addr as *const _ as *const _, len))?;
+            cvt(libc::connect(self.as_raw_fd(), &addr as *const _ as *const _, len))?;
         }
         Ok(())
     }
@@ -229,7 +229,7 @@ impl UnixDatagram {
     /// ```
     #[stable(feature = "unix_socket", since = "1.10.0")]
     pub fn local_addr(&self) -> io::Result<SocketAddr> {
-        SocketAddr::new(|addr, len| unsafe { libc::getsockname(*self.0.as_inner(), addr, len) })
+        SocketAddr::new(|addr, len| unsafe { libc::getsockname(self.as_raw_fd(), addr, len) })
     }
 
     /// Returns the address of this socket's peer.
@@ -253,7 +253,7 @@ impl UnixDatagram {
     /// ```
     #[stable(feature = "unix_socket", since = "1.10.0")]
     pub fn peer_addr(&self) -> io::Result<SocketAddr> {
-        SocketAddr::new(|addr, len| unsafe { libc::getpeername(*self.0.as_inner(), addr, len) })
+        SocketAddr::new(|addr, len| unsafe { libc::getpeername(self.as_raw_fd(), addr, len) })
     }
 
     fn recv_from_flags(
@@ -264,7 +264,7 @@ impl UnixDatagram {
         let mut count = 0;
         let addr = SocketAddr::new(|addr, len| unsafe {
             count = libc::recvfrom(
-                *self.0.as_inner(),
+                self.as_raw_fd(),
                 buf.as_mut_ptr() as *mut _,
                 buf.len(),
                 flags,
@@ -462,7 +462,7 @@ impl UnixDatagram {
             let (addr, len) = sockaddr_un(path.as_ref())?;
 
             let count = cvt(libc::sendto(
-                *self.0.as_inner(),
+                self.as_raw_fd(),
                 buf.as_ptr() as *const _,
                 buf.len(),
                 MSG_NOSIGNAL,
@@ -881,7 +881,7 @@ impl UnixDatagram {
 impl AsRawFd for UnixDatagram {
     #[inline]
     fn as_raw_fd(&self) -> RawFd {
-        *self.0.as_inner()
+        self.0.as_inner().as_raw_fd()
     }
 }
 
@@ -889,7 +889,7 @@ impl AsRawFd for UnixDatagram {
 impl FromRawFd for UnixDatagram {
     #[inline]
     unsafe fn from_raw_fd(fd: RawFd) -> UnixDatagram {
-        UnixDatagram(Socket::from_inner(fd))
+        UnixDatagram(Socket::from_inner(FromInner::from_inner(OwnedFd::from_raw_fd(fd))))
     }
 }
 
@@ -897,6 +897,30 @@ impl FromRawFd for UnixDatagram {
 impl IntoRawFd for UnixDatagram {
     #[inline]
     fn into_raw_fd(self) -> RawFd {
-        self.0.into_inner()
+        self.0.into_inner().into_inner().into_raw_fd()
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl AsFd for UnixDatagram {
+    #[inline]
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.0.as_inner().as_fd()
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl From<UnixDatagram> for OwnedFd {
+    #[inline]
+    fn from(unix_datagram: UnixDatagram) -> OwnedFd {
+        unsafe { OwnedFd::from_raw_fd(unix_datagram.into_raw_fd()) }
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl From<OwnedFd> for UnixDatagram {
+    #[inline]
+    fn from(owned: OwnedFd) -> Self {
+        unsafe { Self::from_raw_fd(owned.into_raw_fd()) }
     }
 }
diff --git a/library/std/src/os/unix/net/listener.rs b/library/std/src/os/unix/net/listener.rs
index bdd08fe8380..9066c71794f 100644
--- a/library/std/src/os/unix/net/listener.rs
+++ b/library/std/src/os/unix/net/listener.rs
@@ -1,5 +1,5 @@
 use super::{sockaddr_un, SocketAddr, UnixStream};
-use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
+use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
 use crate::path::Path;
 use crate::sys::cvt;
 use crate::sys::net::Socket;
@@ -74,8 +74,8 @@ impl UnixListener {
             let inner = Socket::new_raw(libc::AF_UNIX, libc::SOCK_STREAM)?;
             let (addr, len) = sockaddr_un(path.as_ref())?;
 
-            cvt(libc::bind(*inner.as_inner(), &addr as *const _ as *const _, len as _))?;
-            cvt(libc::listen(*inner.as_inner(), 128))?;
+            cvt(libc::bind(inner.as_inner().as_raw_fd(), &addr as *const _ as *const _, len as _))?;
+            cvt(libc::listen(inner.as_inner().as_raw_fd(), 128))?;
 
             Ok(UnixListener(inner))
         }
@@ -150,7 +150,7 @@ impl UnixListener {
     /// ```
     #[stable(feature = "unix_socket", since = "1.10.0")]
     pub fn local_addr(&self) -> io::Result<SocketAddr> {
-        SocketAddr::new(|addr, len| unsafe { libc::getsockname(*self.0.as_inner(), addr, len) })
+        SocketAddr::new(|addr, len| unsafe { libc::getsockname(self.as_raw_fd(), addr, len) })
     }
 
     /// Moves the socket into or out of nonblocking mode.
@@ -242,7 +242,7 @@ impl UnixListener {
 impl AsRawFd for UnixListener {
     #[inline]
     fn as_raw_fd(&self) -> RawFd {
-        *self.0.as_inner()
+        self.0.as_inner().as_raw_fd()
     }
 }
 
@@ -250,7 +250,7 @@ impl AsRawFd for UnixListener {
 impl FromRawFd for UnixListener {
     #[inline]
     unsafe fn from_raw_fd(fd: RawFd) -> UnixListener {
-        UnixListener(Socket::from_inner(fd))
+        UnixListener(Socket::from_inner(FromInner::from_inner(OwnedFd::from_raw_fd(fd))))
     }
 }
 
@@ -258,7 +258,7 @@ impl FromRawFd for UnixListener {
 impl IntoRawFd for UnixListener {
     #[inline]
     fn into_raw_fd(self) -> RawFd {
-        self.0.into_inner()
+        self.0.into_inner().into_inner().into_raw_fd()
     }
 }
 
diff --git a/library/std/src/os/unix/net/mod.rs b/library/std/src/os/unix/net/mod.rs
index 3088ffb5e5c..d462bd4b5f7 100644
--- a/library/std/src/os/unix/net/mod.rs
+++ b/library/std/src/os/unix/net/mod.rs
@@ -25,7 +25,6 @@ mod addr;
 mod ancillary;
 mod datagram;
 mod listener;
-mod raw_fd;
 mod stream;
 #[cfg(all(test, not(target_os = "emscripten")))]
 mod tests;
@@ -48,7 +47,5 @@ pub use self::ancillary::*;
 pub use self::datagram::*;
 #[stable(feature = "unix_socket", since = "1.10.0")]
 pub use self::listener::*;
-#[stable(feature = "rust1", since = "1.0.0")]
-pub use self::raw_fd::*;
 #[stable(feature = "unix_socket", since = "1.10.0")]
 pub use self::stream::*;
diff --git a/library/std/src/os/unix/net/stream.rs b/library/std/src/os/unix/net/stream.rs
index fba084375e5..4119de3c03c 100644
--- a/library/std/src/os/unix/net/stream.rs
+++ b/library/std/src/os/unix/net/stream.rs
@@ -13,7 +13,7 @@ use super::{sockaddr_un, SocketAddr};
 use crate::fmt;
 use crate::io::{self, Initializer, IoSlice, IoSliceMut};
 use crate::net::Shutdown;
-use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
+use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
 #[cfg(any(
     target_os = "android",
     target_os = "linux",
@@ -28,7 +28,7 @@ use crate::os::unix::ucred;
 use crate::path::Path;
 use crate::sys::cvt;
 use crate::sys::net::Socket;
-use crate::sys_common::{AsInner, FromInner, IntoInner};
+use crate::sys_common::{AsInner, FromInner};
 use crate::time::Duration;
 
 #[unstable(feature = "peer_credentials_unix_socket", issue = "42839", reason = "unstable")]
@@ -101,7 +101,7 @@ impl UnixStream {
             let inner = Socket::new_raw(libc::AF_UNIX, libc::SOCK_STREAM)?;
             let (addr, len) = sockaddr_un(path.as_ref())?;
 
-            cvt(libc::connect(*inner.as_inner(), &addr as *const _ as *const _, len))?;
+            cvt(libc::connect(inner.as_raw_fd(), &addr as *const _ as *const _, len))?;
             Ok(UnixStream(inner))
         }
     }
@@ -167,7 +167,7 @@ impl UnixStream {
     /// ```
     #[stable(feature = "unix_socket", since = "1.10.0")]
     pub fn local_addr(&self) -> io::Result<SocketAddr> {
-        SocketAddr::new(|addr, len| unsafe { libc::getsockname(*self.0.as_inner(), addr, len) })
+        SocketAddr::new(|addr, len| unsafe { libc::getsockname(self.as_raw_fd(), addr, len) })
     }
 
     /// Returns the socket address of the remote half of this connection.
@@ -185,7 +185,7 @@ impl UnixStream {
     /// ```
     #[stable(feature = "unix_socket", since = "1.10.0")]
     pub fn peer_addr(&self) -> io::Result<SocketAddr> {
-        SocketAddr::new(|addr, len| unsafe { libc::getpeername(*self.0.as_inner(), addr, len) })
+        SocketAddr::new(|addr, len| unsafe { libc::getpeername(self.as_raw_fd(), addr, len) })
     }
 
     /// Gets the peer credentials for this Unix domain socket.
@@ -659,7 +659,7 @@ impl<'a> io::Write for &'a UnixStream {
 impl AsRawFd for UnixStream {
     #[inline]
     fn as_raw_fd(&self) -> RawFd {
-        *self.0.as_inner()
+        self.0.as_raw_fd()
     }
 }
 
@@ -667,7 +667,7 @@ impl AsRawFd for UnixStream {
 impl FromRawFd for UnixStream {
     #[inline]
     unsafe fn from_raw_fd(fd: RawFd) -> UnixStream {
-        UnixStream(Socket::from_inner(fd))
+        UnixStream(Socket::from_inner(FromInner::from_inner(OwnedFd::from_raw_fd(fd))))
     }
 }
 
@@ -675,6 +675,30 @@ impl FromRawFd for UnixStream {
 impl IntoRawFd for UnixStream {
     #[inline]
     fn into_raw_fd(self) -> RawFd {
-        self.0.into_inner()
+        self.0.into_raw_fd()
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl AsFd for UnixStream {
+    #[inline]
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.0.as_fd()
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl From<UnixStream> for OwnedFd {
+    #[inline]
+    fn from(unix_stream: UnixStream) -> OwnedFd {
+        unsafe { OwnedFd::from_raw_fd(unix_stream.into_raw_fd()) }
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl From<OwnedFd> for UnixStream {
+    #[inline]
+    fn from(owned: OwnedFd) -> Self {
+        unsafe { Self::from_raw_fd(owned.into_raw_fd()) }
     }
 }
diff --git a/library/std/src/os/unix/process.rs b/library/std/src/os/unix/process.rs
index 615290d2703..650dcbabbae 100644
--- a/library/std/src/os/unix/process.rs
+++ b/library/std/src/os/unix/process.rs
@@ -4,7 +4,7 @@
 
 use crate::ffi::OsStr;
 use crate::io;
-use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
+use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
 use crate::process;
 use crate::sealed::Sealed;
 use crate::sys;
@@ -321,7 +321,17 @@ impl ExitStatusExt for process::ExitStatusError {
 impl FromRawFd for process::Stdio {
     #[inline]
     unsafe fn from_raw_fd(fd: RawFd) -> process::Stdio {
-        let fd = sys::fd::FileDesc::new(fd);
+        let fd = sys::fd::FileDesc::from_raw_fd(fd);
+        let io = sys::process::Stdio::Fd(fd);
+        process::Stdio::from_inner(io)
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl From<OwnedFd> for process::Stdio {
+    #[inline]
+    fn from(fd: OwnedFd) -> process::Stdio {
+        let fd = sys::fd::FileDesc::from_inner(fd);
         let io = sys::process::Stdio::Fd(fd);
         process::Stdio::from_inner(io)
     }
@@ -331,7 +341,7 @@ impl FromRawFd for process::Stdio {
 impl AsRawFd for process::ChildStdin {
     #[inline]
     fn as_raw_fd(&self) -> RawFd {
-        self.as_inner().fd().raw()
+        self.as_inner().as_raw_fd()
     }
 }
 
@@ -339,7 +349,7 @@ impl AsRawFd for process::ChildStdin {
 impl AsRawFd for process::ChildStdout {
     #[inline]
     fn as_raw_fd(&self) -> RawFd {
-        self.as_inner().fd().raw()
+        self.as_inner().as_raw_fd()
     }
 }
 
@@ -347,7 +357,7 @@ impl AsRawFd for process::ChildStdout {
 impl AsRawFd for process::ChildStderr {
     #[inline]
     fn as_raw_fd(&self) -> RawFd {
-        self.as_inner().fd().raw()
+        self.as_inner().as_raw_fd()
     }
 }
 
@@ -355,7 +365,7 @@ impl AsRawFd for process::ChildStderr {
 impl IntoRawFd for process::ChildStdin {
     #[inline]
     fn into_raw_fd(self) -> RawFd {
-        self.into_inner().into_fd().into_raw()
+        self.into_inner().into_inner().into_raw_fd()
     }
 }
 
@@ -363,7 +373,7 @@ impl IntoRawFd for process::ChildStdin {
 impl IntoRawFd for process::ChildStdout {
     #[inline]
     fn into_raw_fd(self) -> RawFd {
-        self.into_inner().into_fd().into_raw()
+        self.into_inner().into_inner().into_raw_fd()
     }
 }
 
@@ -371,7 +381,55 @@ impl IntoRawFd for process::ChildStdout {
 impl IntoRawFd for process::ChildStderr {
     #[inline]
     fn into_raw_fd(self) -> RawFd {
-        self.into_inner().into_fd().into_raw()
+        self.into_inner().into_inner().into_raw_fd()
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl AsFd for crate::process::ChildStdin {
+    #[inline]
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.as_inner().as_fd()
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl From<crate::process::ChildStdin> for OwnedFd {
+    #[inline]
+    fn from(child_stdin: crate::process::ChildStdin) -> OwnedFd {
+        child_stdin.into_inner().into_inner().into_inner()
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl AsFd for crate::process::ChildStdout {
+    #[inline]
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.as_inner().as_fd()
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl From<crate::process::ChildStdout> for OwnedFd {
+    #[inline]
+    fn from(child_stdout: crate::process::ChildStdout) -> OwnedFd {
+        child_stdout.into_inner().into_inner().into_inner()
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl AsFd for crate::process::ChildStderr {
+    #[inline]
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.as_inner().as_fd()
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl From<crate::process::ChildStderr> for OwnedFd {
+    #[inline]
+    fn from(child_stderr: crate::process::ChildStderr) -> OwnedFd {
+        child_stderr.into_inner().into_inner().into_inner()
     }
 }
 
diff --git a/library/std/src/os/wasi/fs.rs b/library/std/src/os/wasi/fs.rs
index bd30d6ae3f3..3df27563e21 100644
--- a/library/std/src/os/wasi/fs.rs
+++ b/library/std/src/os/wasi/fs.rs
@@ -228,35 +228,35 @@ pub trait FileExt {
 
 impl FileExt for fs::File {
     fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
-        self.as_inner().fd().pread(bufs, offset)
+        self.as_inner().as_inner().pread(bufs, offset)
     }
 
     fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
-        self.as_inner().fd().pwrite(bufs, offset)
+        self.as_inner().as_inner().pwrite(bufs, offset)
     }
 
     fn tell(&self) -> io::Result<u64> {
-        self.as_inner().fd().tell()
+        self.as_inner().as_inner().tell()
     }
 
     fn fdstat_set_flags(&self, flags: u16) -> io::Result<()> {
-        self.as_inner().fd().set_flags(flags)
+        self.as_inner().as_inner().set_flags(flags)
     }
 
     fn fdstat_set_rights(&self, rights: u64, inheriting: u64) -> io::Result<()> {
-        self.as_inner().fd().set_rights(rights, inheriting)
+        self.as_inner().as_inner().set_rights(rights, inheriting)
     }
 
     fn advise(&self, offset: u64, len: u64, advice: u8) -> io::Result<()> {
-        self.as_inner().fd().advise(offset, len, advice)
+        self.as_inner().as_inner().advise(offset, len, advice)
     }
 
     fn allocate(&self, offset: u64, len: u64) -> io::Result<()> {
-        self.as_inner().fd().allocate(offset, len)
+        self.as_inner().as_inner().allocate(offset, len)
     }
 
     fn create_directory<P: AsRef<Path>>(&self, dir: P) -> io::Result<()> {
-        self.as_inner().fd().create_directory(osstr2str(dir.as_ref().as_ref())?)
+        self.as_inner().as_inner().create_directory(osstr2str(dir.as_ref().as_ref())?)
     }
 
     fn read_link<P: AsRef<Path>>(&self, path: P) -> io::Result<PathBuf> {
@@ -269,11 +269,11 @@ impl FileExt for fs::File {
     }
 
     fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
-        self.as_inner().fd().unlink_file(osstr2str(path.as_ref().as_ref())?)
+        self.as_inner().as_inner().unlink_file(osstr2str(path.as_ref().as_ref())?)
     }
 
     fn remove_directory<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
-        self.as_inner().fd().remove_directory(osstr2str(path.as_ref().as_ref())?)
+        self.as_inner().as_inner().remove_directory(osstr2str(path.as_ref().as_ref())?)
     }
 }
 
@@ -486,10 +486,10 @@ pub fn link<P: AsRef<Path>, U: AsRef<Path>>(
     new_fd: &File,
     new_path: U,
 ) -> io::Result<()> {
-    old_fd.as_inner().fd().link(
+    old_fd.as_inner().as_inner().link(
         old_flags,
         osstr2str(old_path.as_ref().as_ref())?,
-        new_fd.as_inner().fd(),
+        new_fd.as_inner().as_inner(),
         osstr2str(new_path.as_ref().as_ref())?,
     )
 }
@@ -503,9 +503,9 @@ pub fn rename<P: AsRef<Path>, U: AsRef<Path>>(
     new_fd: &File,
     new_path: U,
 ) -> io::Result<()> {
-    old_fd.as_inner().fd().rename(
+    old_fd.as_inner().as_inner().rename(
         osstr2str(old_path.as_ref().as_ref())?,
-        new_fd.as_inner().fd(),
+        new_fd.as_inner().as_inner(),
         osstr2str(new_path.as_ref().as_ref())?,
     )
 }
@@ -519,7 +519,7 @@ pub fn symlink<P: AsRef<Path>, U: AsRef<Path>>(
     new_path: U,
 ) -> io::Result<()> {
     fd.as_inner()
-        .fd()
+        .as_inner()
         .symlink(osstr2str(old_path.as_ref().as_ref())?, osstr2str(new_path.as_ref().as_ref())?)
 }
 
diff --git a/library/std/src/os/wasi/io.rs b/library/std/src/os/wasi/io.rs
deleted file mode 100644
index b6bc74da8e7..00000000000
--- a/library/std/src/os/wasi/io.rs
+++ /dev/null
@@ -1,208 +0,0 @@
-//! WASI-specific extensions to general I/O primitives
-
-#![deny(unsafe_op_in_unsafe_fn)]
-#![unstable(feature = "wasi_ext", issue = "71213")]
-
-use crate::fs;
-use crate::io;
-use crate::net;
-use crate::os::raw;
-use crate::sys;
-use crate::sys_common::{AsInner, FromInner, IntoInner};
-
-/// Raw file descriptors.
-///
-/// This has type `c_int` to ease compatibility with code that also compiles on
-/// Unix configurations, however unlike Unix and POSIX, in WASI negative file
-/// descriptors are valid. Only `-1` is reserved for indicating errors. Code
-/// intending to be portable across Unix platforms and WASI should avoid
-/// assuming that negative file descriptors are invalid.
-pub type RawFd = raw::c_int;
-
-/// A trait to extract the raw WASI file descriptor from an underlying
-/// object.
-pub trait AsRawFd {
-    /// Extracts the raw file descriptor.
-    ///
-    /// This method does **not** pass ownership of the raw file descriptor
-    /// to the caller. The descriptor is only guaranteed to be valid while
-    /// the original object has not yet been destroyed.
-    fn as_raw_fd(&self) -> RawFd;
-}
-
-/// A trait to express the ability to construct an object from a raw file
-/// descriptor.
-pub trait FromRawFd {
-    /// Constructs a new instance of `Self` from the given raw file
-    /// descriptor.
-    ///
-    /// This function **consumes ownership** of the specified file
-    /// descriptor. The returned object will take responsibility for closing
-    /// it when the object goes out of scope.
-    ///
-    /// This function is also unsafe as the primitives currently returned
-    /// have the contract that they are the sole owner of the file
-    /// descriptor they are wrapping. Usage of this function could
-    /// accidentally allow violating this contract which can cause memory
-    /// unsafety in code that relies on it being true.
-    unsafe fn from_raw_fd(fd: RawFd) -> Self;
-}
-
-/// A trait to express the ability to consume an object and acquire ownership of
-/// its raw file descriptor.
-pub trait IntoRawFd {
-    /// Consumes this object, returning the raw underlying file descriptor.
-    ///
-    /// This function **transfers ownership** of the underlying file descriptor
-    /// to the caller. Callers are then the unique owners of the file descriptor
-    /// and must close the descriptor once it's no longer needed.
-    fn into_raw_fd(self) -> RawFd;
-}
-
-#[stable(feature = "raw_fd_reflexive_traits", since = "1.48.0")]
-impl AsRawFd for RawFd {
-    #[inline]
-    fn as_raw_fd(&self) -> RawFd {
-        *self
-    }
-}
-#[stable(feature = "raw_fd_reflexive_traits", since = "1.48.0")]
-impl IntoRawFd for RawFd {
-    #[inline]
-    fn into_raw_fd(self) -> RawFd {
-        self
-    }
-}
-#[stable(feature = "raw_fd_reflexive_traits", since = "1.48.0")]
-impl FromRawFd for RawFd {
-    #[inline]
-    unsafe fn from_raw_fd(fd: RawFd) -> RawFd {
-        fd
-    }
-}
-
-impl AsRawFd for net::TcpStream {
-    #[inline]
-    fn as_raw_fd(&self) -> RawFd {
-        self.as_inner().fd().as_raw()
-    }
-}
-
-impl FromRawFd for net::TcpStream {
-    #[inline]
-    unsafe fn from_raw_fd(fd: RawFd) -> net::TcpStream {
-        net::TcpStream::from_inner(sys::net::TcpStream::from_inner(fd))
-    }
-}
-
-impl IntoRawFd for net::TcpStream {
-    #[inline]
-    fn into_raw_fd(self) -> RawFd {
-        self.into_inner().into_fd().into_raw()
-    }
-}
-
-impl AsRawFd for net::TcpListener {
-    #[inline]
-    fn as_raw_fd(&self) -> RawFd {
-        self.as_inner().fd().as_raw()
-    }
-}
-
-impl FromRawFd for net::TcpListener {
-    #[inline]
-    unsafe fn from_raw_fd(fd: RawFd) -> net::TcpListener {
-        net::TcpListener::from_inner(sys::net::TcpListener::from_inner(fd))
-    }
-}
-
-impl IntoRawFd for net::TcpListener {
-    #[inline]
-    fn into_raw_fd(self) -> RawFd {
-        self.into_inner().into_fd().into_raw()
-    }
-}
-
-impl AsRawFd for net::UdpSocket {
-    #[inline]
-    fn as_raw_fd(&self) -> RawFd {
-        self.as_inner().fd().as_raw()
-    }
-}
-
-impl FromRawFd for net::UdpSocket {
-    #[inline]
-    unsafe fn from_raw_fd(fd: RawFd) -> net::UdpSocket {
-        net::UdpSocket::from_inner(sys::net::UdpSocket::from_inner(fd))
-    }
-}
-
-impl IntoRawFd for net::UdpSocket {
-    #[inline]
-    fn into_raw_fd(self) -> RawFd {
-        self.into_inner().into_fd().into_raw()
-    }
-}
-
-impl AsRawFd for fs::File {
-    #[inline]
-    fn as_raw_fd(&self) -> RawFd {
-        self.as_inner().fd().as_raw()
-    }
-}
-
-impl FromRawFd for fs::File {
-    #[inline]
-    unsafe fn from_raw_fd(fd: RawFd) -> fs::File {
-        fs::File::from_inner(sys::fs::File::from_inner(fd))
-    }
-}
-
-impl IntoRawFd for fs::File {
-    #[inline]
-    fn into_raw_fd(self) -> RawFd {
-        self.into_inner().into_fd().into_raw()
-    }
-}
-
-impl AsRawFd for io::Stdin {
-    #[inline]
-    fn as_raw_fd(&self) -> RawFd {
-        libc::STDIN_FILENO
-    }
-}
-
-impl AsRawFd for io::Stdout {
-    #[inline]
-    fn as_raw_fd(&self) -> RawFd {
-        libc::STDOUT_FILENO
-    }
-}
-
-impl AsRawFd for io::Stderr {
-    #[inline]
-    fn as_raw_fd(&self) -> RawFd {
-        libc::STDERR_FILENO
-    }
-}
-
-impl<'a> AsRawFd for io::StdinLock<'a> {
-    #[inline]
-    fn as_raw_fd(&self) -> RawFd {
-        libc::STDIN_FILENO
-    }
-}
-
-impl<'a> AsRawFd for io::StdoutLock<'a> {
-    #[inline]
-    fn as_raw_fd(&self) -> RawFd {
-        libc::STDOUT_FILENO
-    }
-}
-
-impl<'a> AsRawFd for io::StderrLock<'a> {
-    #[inline]
-    fn as_raw_fd(&self) -> RawFd {
-        libc::STDERR_FILENO
-    }
-}
diff --git a/library/std/src/os/wasi/io/fd.rs b/library/std/src/os/wasi/io/fd.rs
new file mode 100644
index 00000000000..930aca887e3
--- /dev/null
+++ b/library/std/src/os/wasi/io/fd.rs
@@ -0,0 +1,9 @@
+//! Owned and borrowed file descriptors.
+
+#![unstable(feature = "wasi_ext", issue = "71213")]
+
+// Tests for this module
+#[cfg(test)]
+mod tests;
+
+pub use crate::os::fd::owned::*;
diff --git a/library/std/src/os/wasi/io/fd/tests.rs b/library/std/src/os/wasi/io/fd/tests.rs
new file mode 100644
index 00000000000..418274752b0
--- /dev/null
+++ b/library/std/src/os/wasi/io/fd/tests.rs
@@ -0,0 +1,11 @@
+use crate::mem::size_of;
+use crate::os::wasi::io::RawFd;
+
+#[test]
+fn test_raw_fd_layout() {
+    // `OwnedFd` and `BorrowedFd` use `rustc_layout_scalar_valid_range_start`
+    // and `rustc_layout_scalar_valid_range_end`, with values that depend on
+    // the bit width of `RawFd`. If this ever changes, those values will need
+    // to be updated.
+    assert_eq!(size_of::<RawFd>(), 4);
+}
diff --git a/library/std/src/os/wasi/io/mod.rs b/library/std/src/os/wasi/io/mod.rs
new file mode 100644
index 00000000000..6c884e2eaf4
--- /dev/null
+++ b/library/std/src/os/wasi/io/mod.rs
@@ -0,0 +1,12 @@
+//! WASI-specific extensions to general I/O primitives.
+
+#![deny(unsafe_op_in_unsafe_fn)]
+#![unstable(feature = "wasi_ext", issue = "71213")]
+
+mod fd;
+mod raw;
+
+#[unstable(feature = "wasi_ext", issue = "71213")]
+pub use fd::*;
+#[unstable(feature = "wasi_ext", issue = "71213")]
+pub use raw::*;
diff --git a/library/std/src/os/wasi/io/raw.rs b/library/std/src/os/wasi/io/raw.rs
new file mode 100644
index 00000000000..0e0c5824e34
--- /dev/null
+++ b/library/std/src/os/wasi/io/raw.rs
@@ -0,0 +1,5 @@
+//! WASI-specific extensions to general I/O primitives.
+
+#![unstable(feature = "wasi_ext", issue = "71213")]
+
+pub use crate::os::fd::raw::*;
diff --git a/library/std/src/os/wasi/mod.rs b/library/std/src/os/wasi/mod.rs
index 44b7c32e956..d767c149dc5 100644
--- a/library/std/src/os/wasi/mod.rs
+++ b/library/std/src/os/wasi/mod.rs
@@ -32,6 +32,7 @@
 pub mod ffi;
 pub mod fs;
 pub mod io;
+pub mod net;
 
 /// A prelude for conveniently writing platform-specific code.
 ///
@@ -49,5 +50,5 @@ pub mod prelude {
     pub use super::fs::{DirEntryExt, FileExt, MetadataExt, OpenOptionsExt};
     #[doc(no_inline)]
     #[stable(feature = "rust1", since = "1.0.0")]
-    pub use super::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
+    pub use super::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
 }
diff --git a/library/std/src/os/wasi/net/mod.rs b/library/std/src/os/wasi/net/mod.rs
new file mode 100644
index 00000000000..e6bcf87887f
--- /dev/null
+++ b/library/std/src/os/wasi/net/mod.rs
@@ -0,0 +1,3 @@
+//! WASI-specific networking functionality
+
+#![unstable(feature = "wasi_ext", issue = "71213")]
diff --git a/library/std/src/os/windows/io/handle.rs b/library/std/src/os/windows/io/handle.rs
new file mode 100644
index 00000000000..72a17adb3b4
--- /dev/null
+++ b/library/std/src/os/windows/io/handle.rs
@@ -0,0 +1,386 @@
+//! Owned and borrowed OS handles.
+
+#![unstable(feature = "io_safety", issue = "87074")]
+
+use super::raw::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle};
+use crate::convert::TryFrom;
+use crate::ffi::c_void;
+use crate::fmt;
+use crate::fs;
+use crate::marker::PhantomData;
+use crate::mem::forget;
+use crate::ptr::NonNull;
+use crate::sys::c;
+use crate::sys_common::{AsInner, FromInner, IntoInner};
+
+/// A borrowed handle.
+///
+/// This has a lifetime parameter to tie it to the lifetime of something that
+/// owns the handle.
+///
+/// This uses `repr(transparent)` and has the representation of a host handle,
+/// so it can be used in FFI in places where a handle is passed as an argument,
+/// it is not captured or consumed, and it is never null.
+///
+/// Note that it *may* have the value `INVALID_HANDLE_VALUE` (-1), which is
+/// sometimes a valid handle value. See [here] for the full story.
+///
+/// [here]: https://devblogs.microsoft.com/oldnewthing/20040302-00/?p=40443
+#[derive(Copy, Clone)]
+#[repr(transparent)]
+#[unstable(feature = "io_safety", issue = "87074")]
+pub struct BorrowedHandle<'handle> {
+    handle: NonNull<c_void>,
+    _phantom: PhantomData<&'handle OwnedHandle>,
+}
+
+/// An owned handle.
+///
+/// This closes the handle on drop.
+///
+/// This uses `repr(transparent)` and has the representation of a host handle,
+/// so it can be used in FFI in places where a handle is passed as a consumed
+/// argument or returned as an owned value, and is never null.
+///
+/// Note that it *may* have the value `INVALID_HANDLE_VALUE` (-1), which is
+/// sometimes a valid handle value. See [here] for the full story. For APIs
+/// like `CreateFileW` which report errors with `INVALID_HANDLE_VALUE` instead
+/// of null, use [`HandleOrInvalid`] instead of `Option<OwnedHandle>`.
+///
+/// `OwnedHandle` uses [`CloseHandle`] to close its handle on drop. As such,
+/// it must not be used with handles to open registry keys which need to be
+/// closed with [`RegCloseKey`] instead.
+///
+/// [`CloseHandle`]: https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle
+/// [`RegCloseKey`]: https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regclosekey
+///
+/// [here]: https://devblogs.microsoft.com/oldnewthing/20040302-00/?p=40443
+#[repr(transparent)]
+#[unstable(feature = "io_safety", issue = "87074")]
+pub struct OwnedHandle {
+    handle: NonNull<c_void>,
+}
+
+/// FFI type for handles in return values or out parameters, where `INVALID_HANDLE_VALUE` is used
+/// as a sentry value to indicate errors, such as in the return value of `CreateFileW`. This uses
+/// `repr(transparent)` and has the representation of a host handle, so that it can be used in such
+/// FFI declarations.
+///
+/// The only thing you can usefully do with a `HandleOrInvalid` is to convert it into an
+/// `OwnedHandle` using its [`TryFrom`] implementation; this conversion takes care of the check for
+/// `INVALID_HANDLE_VALUE`. This ensures that such FFI calls cannot start using the handle without
+/// checking for `INVALID_HANDLE_VALUE` first.
+///
+/// If this holds a valid handle, it will close the handle on drop.
+#[repr(transparent)]
+#[unstable(feature = "io_safety", issue = "87074")]
+#[derive(Debug)]
+pub struct HandleOrInvalid(Option<OwnedHandle>);
+
+// The Windows [`HANDLE`] type may be transferred across and shared between
+// thread boundaries (despite containing a `*mut void`, which in general isn't
+// `Send` or `Sync`).
+//
+// [`HANDLE`]: std::os::windows::raw::HANDLE
+unsafe impl Send for OwnedHandle {}
+unsafe impl Send for HandleOrInvalid {}
+unsafe impl Send for BorrowedHandle<'_> {}
+unsafe impl Sync for OwnedHandle {}
+unsafe impl Sync for HandleOrInvalid {}
+unsafe impl Sync for BorrowedHandle<'_> {}
+
+impl BorrowedHandle<'_> {
+    /// Return a `BorrowedHandle` holding the given raw handle.
+    ///
+    /// # Safety
+    ///
+    /// The resource pointed to by `handle` must be a valid open handle, it
+    /// must remain open for the duration of the returned `BorrowedHandle`, and
+    /// it must not be null.
+    ///
+    /// Note that it *may* have the value `INVALID_HANDLE_VALUE` (-1), which is
+    /// sometimes a valid handle value. See [here] for the full story.
+    ///
+    /// [here]: https://devblogs.microsoft.com/oldnewthing/20040302-00/?p=40443
+    #[inline]
+    #[unstable(feature = "io_safety", issue = "87074")]
+    pub unsafe fn borrow_raw_handle(handle: RawHandle) -> Self {
+        assert!(!handle.is_null());
+        Self { handle: NonNull::new_unchecked(handle), _phantom: PhantomData }
+    }
+}
+
+impl TryFrom<HandleOrInvalid> for OwnedHandle {
+    type Error = ();
+
+    #[inline]
+    fn try_from(handle_or_invalid: HandleOrInvalid) -> Result<Self, ()> {
+        // In theory, we ought to be able to assume that the pointer here is
+        // never null, use `OwnedHandle` rather than `Option<OwnedHandle>`, and
+        // obviate the the panic path here.  Unfortunately, Win32 documentation
+        // doesn't explicitly guarantee this anywhere.
+        //
+        // APIs like [`CreateFileW`] itself have `HANDLE` arguments where a
+        // null handle indicates an absent value, which wouldn't work if null
+        // were a valid handle value, so it seems very unlikely that it could
+        // ever return null. But who knows?
+        //
+        // [`CreateFileW`]: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
+        let owned_handle = handle_or_invalid.0.expect("A `HandleOrInvalid` was null!");
+        if owned_handle.handle.as_ptr() == c::INVALID_HANDLE_VALUE {
+            Err(())
+        } else {
+            Ok(owned_handle)
+        }
+    }
+}
+
+impl AsRawHandle for BorrowedHandle<'_> {
+    #[inline]
+    fn as_raw_handle(&self) -> RawHandle {
+        self.handle.as_ptr()
+    }
+}
+
+impl AsRawHandle for OwnedHandle {
+    #[inline]
+    fn as_raw_handle(&self) -> RawHandle {
+        self.handle.as_ptr()
+    }
+}
+
+impl IntoRawHandle for OwnedHandle {
+    #[inline]
+    fn into_raw_handle(self) -> RawHandle {
+        let handle = self.handle.as_ptr();
+        forget(self);
+        handle
+    }
+}
+
+impl FromRawHandle for OwnedHandle {
+    /// Constructs a new instance of `Self` from the given raw handle.
+    ///
+    /// Use `HandleOrInvalid` instead of `Option<OwnedHandle>` for APIs that
+    /// use `INVALID_HANDLE_VALUE` to indicate failure.
+    ///
+    /// # Safety
+    ///
+    /// The resource pointed to by `handle` must be open and suitable for
+    /// assuming ownership. The resource must not require any cleanup other
+    /// than `CloseHandle`.
+    ///
+    /// In particular, it must not be used with handles to open registry
+    /// keys which need to be closed with [`RegCloseKey`] instead.
+    ///
+    /// Note that it *may* have the value `INVALID_HANDLE_VALUE` (-1), which is
+    /// sometimes a valid handle value. See [here] for the full story.
+    ///
+    /// [`RegCloseKey`]: https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regclosekey
+    /// [here]: https://devblogs.microsoft.com/oldnewthing/20040302-00/?p=40443
+    #[inline]
+    unsafe fn from_raw_handle(handle: RawHandle) -> Self {
+        assert!(!handle.is_null());
+        Self { handle: NonNull::new_unchecked(handle) }
+    }
+}
+
+impl FromRawHandle for HandleOrInvalid {
+    /// Constructs a new instance of `Self` from the given `RawHandle` returned
+    /// from a Windows API that uses `INVALID_HANDLE_VALUE` to indicate
+    /// failure, such as `CreateFileW`.
+    ///
+    /// Use `Option<OwnedHandle>` instead of `HandleOrInvalid` for APIs that
+    /// use null to indicate failure.
+    ///
+    /// # Safety
+    ///
+    /// The resource pointed to by `handle` must be either open and otherwise
+    /// unowned, or equal to `INVALID_HANDLE_VALUE` (-1). It must not be null.
+    /// Note that not all Windows APIs use `INVALID_HANDLE_VALUE` for errors;
+    /// see [here] for the full story.
+    ///
+    /// [here]: https://devblogs.microsoft.com/oldnewthing/20040302-00/?p=40443
+    #[inline]
+    unsafe fn from_raw_handle(handle: RawHandle) -> Self {
+        // We require non-null here to catch errors earlier.
+        Self(Some(OwnedHandle::from_raw_handle(handle)))
+    }
+}
+
+impl Drop for OwnedHandle {
+    #[inline]
+    fn drop(&mut self) {
+        unsafe {
+            let _ = c::CloseHandle(self.handle.as_ptr());
+        }
+    }
+}
+
+impl fmt::Debug for BorrowedHandle<'_> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("BorrowedHandle").field("handle", &self.handle).finish()
+    }
+}
+
+impl fmt::Debug for OwnedHandle {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("OwnedHandle").field("handle", &self.handle).finish()
+    }
+}
+
+/// A trait to borrow the handle from an underlying object.
+#[unstable(feature = "io_safety", issue = "87074")]
+pub trait AsHandle {
+    /// Borrows the handle.
+    ///
+    /// # Example
+    ///
+    /// ```rust,no_run
+    /// # #![feature(io_safety)]
+    /// use std::fs::File;
+    /// # use std::io;
+    /// use std::os::windows::io::{AsHandle, BorrowedHandle};
+    ///
+    /// let mut f = File::open("foo.txt")?;
+    /// let borrowed_handle: BorrowedHandle<'_> = f.as_handle();
+    /// # Ok::<(), io::Error>(())
+    /// ```
+    fn as_handle(&self) -> BorrowedHandle<'_>;
+}
+
+impl AsHandle for BorrowedHandle<'_> {
+    #[inline]
+    fn as_handle(&self) -> BorrowedHandle<'_> {
+        *self
+    }
+}
+
+impl AsHandle for OwnedHandle {
+    #[inline]
+    fn as_handle(&self) -> BorrowedHandle<'_> {
+        // Safety: `OwnedHandle` and `BorrowedHandle` have the same validity
+        // invariants, and the `BorrowdHandle` is bounded by the lifetime
+        // of `&self`.
+        unsafe { BorrowedHandle::borrow_raw_handle(self.as_raw_handle()) }
+    }
+}
+
+impl AsHandle for fs::File {
+    #[inline]
+    fn as_handle(&self) -> BorrowedHandle<'_> {
+        self.as_inner().as_handle()
+    }
+}
+
+impl From<fs::File> for OwnedHandle {
+    #[inline]
+    fn from(file: fs::File) -> OwnedHandle {
+        file.into_inner().into_inner().into_inner().into()
+    }
+}
+
+impl From<OwnedHandle> for fs::File {
+    #[inline]
+    fn from(owned: OwnedHandle) -> Self {
+        Self::from_inner(FromInner::from_inner(FromInner::from_inner(owned)))
+    }
+}
+
+impl AsHandle for crate::io::Stdin {
+    #[inline]
+    fn as_handle(&self) -> BorrowedHandle<'_> {
+        unsafe { BorrowedHandle::borrow_raw_handle(self.as_raw_handle()) }
+    }
+}
+
+impl<'a> AsHandle for crate::io::StdinLock<'a> {
+    #[inline]
+    fn as_handle(&self) -> BorrowedHandle<'_> {
+        unsafe { BorrowedHandle::borrow_raw_handle(self.as_raw_handle()) }
+    }
+}
+
+impl AsHandle for crate::io::Stdout {
+    #[inline]
+    fn as_handle(&self) -> BorrowedHandle<'_> {
+        unsafe { BorrowedHandle::borrow_raw_handle(self.as_raw_handle()) }
+    }
+}
+
+impl<'a> AsHandle for crate::io::StdoutLock<'a> {
+    #[inline]
+    fn as_handle(&self) -> BorrowedHandle<'_> {
+        unsafe { BorrowedHandle::borrow_raw_handle(self.as_raw_handle()) }
+    }
+}
+
+impl AsHandle for crate::io::Stderr {
+    #[inline]
+    fn as_handle(&self) -> BorrowedHandle<'_> {
+        unsafe { BorrowedHandle::borrow_raw_handle(self.as_raw_handle()) }
+    }
+}
+
+impl<'a> AsHandle for crate::io::StderrLock<'a> {
+    #[inline]
+    fn as_handle(&self) -> BorrowedHandle<'_> {
+        unsafe { BorrowedHandle::borrow_raw_handle(self.as_raw_handle()) }
+    }
+}
+
+impl AsHandle for crate::process::ChildStdin {
+    #[inline]
+    fn as_handle(&self) -> BorrowedHandle<'_> {
+        unsafe { BorrowedHandle::borrow_raw_handle(self.as_raw_handle()) }
+    }
+}
+
+impl From<crate::process::ChildStdin> for OwnedHandle {
+    #[inline]
+    fn from(child_stdin: crate::process::ChildStdin) -> OwnedHandle {
+        unsafe { OwnedHandle::from_raw_handle(child_stdin.into_raw_handle()) }
+    }
+}
+
+impl AsHandle for crate::process::ChildStdout {
+    #[inline]
+    fn as_handle(&self) -> BorrowedHandle<'_> {
+        unsafe { BorrowedHandle::borrow_raw_handle(self.as_raw_handle()) }
+    }
+}
+
+impl From<crate::process::ChildStdout> for OwnedHandle {
+    #[inline]
+    fn from(child_stdout: crate::process::ChildStdout) -> OwnedHandle {
+        unsafe { OwnedHandle::from_raw_handle(child_stdout.into_raw_handle()) }
+    }
+}
+
+impl AsHandle for crate::process::ChildStderr {
+    #[inline]
+    fn as_handle(&self) -> BorrowedHandle<'_> {
+        unsafe { BorrowedHandle::borrow_raw_handle(self.as_raw_handle()) }
+    }
+}
+
+impl From<crate::process::ChildStderr> for OwnedHandle {
+    #[inline]
+    fn from(child_stderr: crate::process::ChildStderr) -> OwnedHandle {
+        unsafe { OwnedHandle::from_raw_handle(child_stderr.into_raw_handle()) }
+    }
+}
+
+impl<T> AsHandle for crate::thread::JoinHandle<T> {
+    #[inline]
+    fn as_handle(&self) -> BorrowedHandle<'_> {
+        unsafe { BorrowedHandle::borrow_raw_handle(self.as_raw_handle()) }
+    }
+}
+
+impl<T> From<crate::thread::JoinHandle<T>> for OwnedHandle {
+    #[inline]
+    fn from(join_handle: crate::thread::JoinHandle<T>) -> OwnedHandle {
+        join_handle.into_inner().into_handle().into_inner()
+    }
+}
diff --git a/library/std/src/os/windows/io/mod.rs b/library/std/src/os/windows/io/mod.rs
new file mode 100644
index 00000000000..2f6f0769548
--- /dev/null
+++ b/library/std/src/os/windows/io/mod.rs
@@ -0,0 +1,56 @@
+//! Windows-specific extensions to general I/O primitives.
+//!
+//! Just like raw pointers, raw Windows handles and sockets point to resources
+//! with dynamic lifetimes, and they can dangle if they outlive their resources
+//! or be forged if they're created from invalid values.
+//!
+//! This module provides three types for representing raw handles and sockets
+//! with different ownership properties: raw, borrowed, and owned, which are
+//! analogous to types used for representing pointers:
+//!
+//! | Type                   | Analogous to |
+//! | ---------------------- | ------------ |
+//! | [`RawHandle`]          | `*const _`   |
+//! | [`RawSocket`]          | `*const _`   |
+//! |                        |              |
+//! | [`BorrowedHandle<'a>`] | `&'a _`      |
+//! | [`BorrowedSocket<'a>`] | `&'a _`      |
+//! |                        |              |
+//! | [`OwnedHandle`]        | `Box<_>`     |
+//! | [`OwnedSocket`]        | `Box<_>`     |
+//!
+//! Like raw pointers, `RawHandle` and `RawSocket` values are primitive values.
+//! And in new code, they should be considered unsafe to do I/O on (analogous
+//! to dereferencing them). Rust did not always provide this guidance, so
+//! existing code in the Rust ecosystem often doesn't mark `RawHandle` and
+//! `RawSocket` usage as unsafe. Once the `io_safety` feature is stable,
+//! libraries will be encouraged to migrate, either by adding `unsafe` to APIs
+//! that dereference `RawHandle` and `RawSocket` values, or by using to
+//! `BorrowedHandle`, `BorrowedSocket`, `OwnedHandle`, or `OwnedSocket`.
+//!
+//! Like references, `BorrowedHandle` and `BorrowedSocket` values are tied to a
+//! lifetime, to ensure that they don't outlive the resource they point to.
+//! These are safe to use. `BorrowedHandle` and `BorrowedSocket` values may be
+//! used in APIs which provide safe access to any system call except for
+//! `CloseHandle`, `closesocket`, or any other call that would end the
+//! dynamic lifetime of the resource without ending the lifetime of the
+//! handle or socket.
+//!
+//! Like boxes, `OwnedHandle` and `OwnedSocket` values conceptually own the
+//! resource they point to, and free (close) it when they are dropped.
+//!
+//! [`BorrowedHandle<'a>`]: crate::os::windows::io::BorrowedHandle
+//! [`BorrowedSocket<'a>`]: crate::os::windows::io::BorrowedSocket
+
+#![stable(feature = "rust1", since = "1.0.0")]
+
+mod handle;
+mod raw;
+mod socket;
+
+#[unstable(feature = "io_safety", issue = "87074")]
+pub use handle::*;
+#[stable(feature = "rust1", since = "1.0.0")]
+pub use raw::*;
+#[unstable(feature = "io_safety", issue = "87074")]
+pub use socket::*;
diff --git a/library/std/src/os/windows/io.rs b/library/std/src/os/windows/io/raw.rs
index 31b5d015ed0..c7f122048a1 100644
--- a/library/std/src/os/windows/io.rs
+++ b/library/std/src/os/windows/io/raw.rs
@@ -5,6 +5,7 @@
 use crate::fs;
 use crate::io;
 use crate::net;
+use crate::os::windows::io::{OwnedHandle, OwnedSocket};
 use crate::os::windows::raw;
 use crate::sys;
 use crate::sys::c;
@@ -61,7 +62,7 @@ pub trait IntoRawHandle {
 impl AsRawHandle for fs::File {
     #[inline]
     fn as_raw_handle(&self) -> RawHandle {
-        self.as_inner().handle().raw() as RawHandle
+        self.as_inner().as_raw_handle() as RawHandle
     }
 }
 
@@ -112,7 +113,9 @@ impl FromRawHandle for fs::File {
     #[inline]
     unsafe fn from_raw_handle(handle: RawHandle) -> fs::File {
         let handle = handle as c::HANDLE;
-        fs::File::from_inner(sys::fs::File::from_inner(handle))
+        fs::File::from_inner(sys::fs::File::from_inner(FromInner::from_inner(
+            OwnedHandle::from_raw_handle(handle),
+        )))
     }
 }
 
@@ -120,7 +123,7 @@ impl FromRawHandle for fs::File {
 impl IntoRawHandle for fs::File {
     #[inline]
     fn into_raw_handle(self) -> RawHandle {
-        self.into_inner().into_handle().into_raw() as *mut _
+        self.into_inner().into_raw_handle() as *mut _
     }
 }
 
@@ -166,21 +169,21 @@ pub trait IntoRawSocket {
 impl AsRawSocket for net::TcpStream {
     #[inline]
     fn as_raw_socket(&self) -> RawSocket {
-        *self.as_inner().socket().as_inner()
+        self.as_inner().socket().as_raw_socket()
     }
 }
 #[stable(feature = "rust1", since = "1.0.0")]
 impl AsRawSocket for net::TcpListener {
     #[inline]
     fn as_raw_socket(&self) -> RawSocket {
-        *self.as_inner().socket().as_inner()
+        self.as_inner().socket().as_raw_socket()
     }
 }
 #[stable(feature = "rust1", since = "1.0.0")]
 impl AsRawSocket for net::UdpSocket {
     #[inline]
     fn as_raw_socket(&self) -> RawSocket {
-        *self.as_inner().socket().as_inner()
+        self.as_inner().socket().as_raw_socket()
     }
 }
 
@@ -188,7 +191,7 @@ impl AsRawSocket for net::UdpSocket {
 impl FromRawSocket for net::TcpStream {
     #[inline]
     unsafe fn from_raw_socket(sock: RawSocket) -> net::TcpStream {
-        let sock = sys::net::Socket::from_inner(sock);
+        let sock = sys::net::Socket::from_inner(OwnedSocket::from_raw_socket(sock));
         net::TcpStream::from_inner(sys_common::net::TcpStream::from_inner(sock))
     }
 }
@@ -196,7 +199,7 @@ impl FromRawSocket for net::TcpStream {
 impl FromRawSocket for net::TcpListener {
     #[inline]
     unsafe fn from_raw_socket(sock: RawSocket) -> net::TcpListener {
-        let sock = sys::net::Socket::from_inner(sock);
+        let sock = sys::net::Socket::from_inner(OwnedSocket::from_raw_socket(sock));
         net::TcpListener::from_inner(sys_common::net::TcpListener::from_inner(sock))
     }
 }
@@ -204,7 +207,7 @@ impl FromRawSocket for net::TcpListener {
 impl FromRawSocket for net::UdpSocket {
     #[inline]
     unsafe fn from_raw_socket(sock: RawSocket) -> net::UdpSocket {
-        let sock = sys::net::Socket::from_inner(sock);
+        let sock = sys::net::Socket::from_inner(OwnedSocket::from_raw_socket(sock));
         net::UdpSocket::from_inner(sys_common::net::UdpSocket::from_inner(sock))
     }
 }
@@ -213,7 +216,7 @@ impl FromRawSocket for net::UdpSocket {
 impl IntoRawSocket for net::TcpStream {
     #[inline]
     fn into_raw_socket(self) -> RawSocket {
-        self.into_inner().into_socket().into_inner()
+        self.into_inner().into_socket().into_inner().into_raw_socket()
     }
 }
 
@@ -221,7 +224,7 @@ impl IntoRawSocket for net::TcpStream {
 impl IntoRawSocket for net::TcpListener {
     #[inline]
     fn into_raw_socket(self) -> RawSocket {
-        self.into_inner().into_socket().into_inner()
+        self.into_inner().into_socket().into_inner().into_raw_socket()
     }
 }
 
@@ -229,6 +232,6 @@ impl IntoRawSocket for net::TcpListener {
 impl IntoRawSocket for net::UdpSocket {
     #[inline]
     fn into_raw_socket(self) -> RawSocket {
-        self.into_inner().into_socket().into_inner()
+        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
new file mode 100644
index 00000000000..23db66df09f
--- /dev/null
+++ b/library/std/src/os/windows/io/socket.rs
@@ -0,0 +1,216 @@
+//! Owned and borrowed OS sockets.
+
+#![unstable(feature = "io_safety", issue = "87074")]
+
+use super::raw::{AsRawSocket, FromRawSocket, IntoRawSocket, RawSocket};
+use crate::fmt;
+use crate::marker::PhantomData;
+use crate::mem::forget;
+use crate::sys::c;
+
+/// A borrowed socket.
+///
+/// This has a lifetime parameter to tie it to the lifetime of something that
+/// owns the socket.
+///
+/// This uses `repr(transparent)` and has the representation of a host socket,
+/// so it can be used in FFI in places where a socket is passed as an argument,
+/// it is not captured or consumed, and it never has the value
+/// `INVALID_SOCKET`.
+#[derive(Copy, Clone)]
+#[repr(transparent)]
+#[rustc_layout_scalar_valid_range_start(0)]
+// This is -2, in two's complement. -1 is `INVALID_SOCKET`.
+#[cfg_attr(target_pointer_width = "32", rustc_layout_scalar_valid_range_end(0xFF_FF_FF_FE))]
+#[cfg_attr(
+    target_pointer_width = "64",
+    rustc_layout_scalar_valid_range_end(0xFF_FF_FF_FF_FF_FF_FF_FE)
+)]
+#[unstable(feature = "io_safety", issue = "87074")]
+pub struct BorrowedSocket<'socket> {
+    socket: RawSocket,
+    _phantom: PhantomData<&'socket OwnedSocket>,
+}
+
+/// An owned socket.
+///
+/// This closes the socket on drop.
+///
+/// This uses `repr(transparent)` and has the representation of a host socket,
+/// so it can be used in FFI in places where a socket is passed as a consumed
+/// argument or returned as an owned value, and it never has the value
+/// `INVALID_SOCKET`.
+#[repr(transparent)]
+#[rustc_layout_scalar_valid_range_start(0)]
+// This is -2, in two's complement. -1 is `INVALID_SOCKET`.
+#[cfg_attr(target_pointer_width = "32", rustc_layout_scalar_valid_range_end(0xFF_FF_FF_FE))]
+#[cfg_attr(
+    target_pointer_width = "64",
+    rustc_layout_scalar_valid_range_end(0xFF_FF_FF_FF_FF_FF_FF_FE)
+)]
+#[unstable(feature = "io_safety", issue = "87074")]
+pub struct OwnedSocket {
+    socket: RawSocket,
+}
+
+impl BorrowedSocket<'_> {
+    /// Return a `BorrowedSocket` holding the given raw socket.
+    ///
+    /// # Safety
+    ///
+    /// The resource pointed to by `raw` must remain open for the duration of
+    /// the returned `BorrowedSocket`, and it must not have the value
+    /// `INVALID_SOCKET`.
+    #[inline]
+    #[unstable(feature = "io_safety", issue = "87074")]
+    pub unsafe fn borrow_raw_socket(socket: RawSocket) -> Self {
+        debug_assert_ne!(socket, c::INVALID_SOCKET as RawSocket);
+        Self { socket, _phantom: PhantomData }
+    }
+}
+
+impl AsRawSocket for BorrowedSocket<'_> {
+    #[inline]
+    fn as_raw_socket(&self) -> RawSocket {
+        self.socket
+    }
+}
+
+impl AsRawSocket for OwnedSocket {
+    #[inline]
+    fn as_raw_socket(&self) -> RawSocket {
+        self.socket
+    }
+}
+
+impl IntoRawSocket for OwnedSocket {
+    #[inline]
+    fn into_raw_socket(self) -> RawSocket {
+        let socket = self.socket;
+        forget(self);
+        socket
+    }
+}
+
+impl FromRawSocket for OwnedSocket {
+    /// Constructs a new instance of `Self` from the given raw socket.
+    ///
+    /// # Safety
+    ///
+    /// The resource pointed to by `socket` must be open and suitable for
+    /// assuming ownership. The resource must not require cleanup other than
+    /// `closesocket`.
+    #[inline]
+    unsafe fn from_raw_socket(socket: RawSocket) -> Self {
+        debug_assert_ne!(socket, c::INVALID_SOCKET as RawSocket);
+        Self { socket }
+    }
+}
+
+impl Drop for OwnedSocket {
+    #[inline]
+    fn drop(&mut self) {
+        unsafe {
+            let _ = c::closesocket(self.socket);
+        }
+    }
+}
+
+impl fmt::Debug for BorrowedSocket<'_> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("BorrowedSocket").field("socket", &self.socket).finish()
+    }
+}
+
+impl fmt::Debug for OwnedSocket {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("OwnedSocket").field("socket", &self.socket).finish()
+    }
+}
+
+/// A trait to borrow the socket from an underlying object.
+#[unstable(feature = "io_safety", issue = "87074")]
+pub trait AsSocket {
+    /// Borrows the socket.
+    fn as_socket(&self) -> BorrowedSocket<'_>;
+}
+
+impl AsSocket for BorrowedSocket<'_> {
+    #[inline]
+    fn as_socket(&self) -> BorrowedSocket<'_> {
+        *self
+    }
+}
+
+impl AsSocket for OwnedSocket {
+    #[inline]
+    fn as_socket(&self) -> BorrowedSocket<'_> {
+        // Safety: `OwnedSocket` and `BorrowedSocket` have the same validity
+        // invariants, and the `BorrowdSocket` is bounded by the lifetime
+        // of `&self`.
+        unsafe { BorrowedSocket::borrow_raw_socket(self.as_raw_socket()) }
+    }
+}
+
+impl AsSocket for crate::net::TcpStream {
+    #[inline]
+    fn as_socket(&self) -> BorrowedSocket<'_> {
+        unsafe { BorrowedSocket::borrow_raw_socket(self.as_raw_socket()) }
+    }
+}
+
+impl From<crate::net::TcpStream> for OwnedSocket {
+    #[inline]
+    fn from(tcp_stream: crate::net::TcpStream) -> OwnedSocket {
+        unsafe { OwnedSocket::from_raw_socket(tcp_stream.into_raw_socket()) }
+    }
+}
+
+impl From<OwnedSocket> for crate::net::TcpStream {
+    #[inline]
+    fn from(owned: OwnedSocket) -> Self {
+        unsafe { Self::from_raw_socket(owned.into_raw_socket()) }
+    }
+}
+
+impl AsSocket for crate::net::TcpListener {
+    #[inline]
+    fn as_socket(&self) -> BorrowedSocket<'_> {
+        unsafe { BorrowedSocket::borrow_raw_socket(self.as_raw_socket()) }
+    }
+}
+
+impl From<crate::net::TcpListener> for OwnedSocket {
+    #[inline]
+    fn from(tcp_listener: crate::net::TcpListener) -> OwnedSocket {
+        unsafe { OwnedSocket::from_raw_socket(tcp_listener.into_raw_socket()) }
+    }
+}
+
+impl From<OwnedSocket> for crate::net::TcpListener {
+    #[inline]
+    fn from(owned: OwnedSocket) -> Self {
+        unsafe { Self::from_raw_socket(owned.into_raw_socket()) }
+    }
+}
+
+impl AsSocket for crate::net::UdpSocket {
+    #[inline]
+    fn as_socket(&self) -> BorrowedSocket<'_> {
+        unsafe { BorrowedSocket::borrow_raw_socket(self.as_raw_socket()) }
+    }
+}
+
+impl From<crate::net::UdpSocket> for OwnedSocket {
+    #[inline]
+    fn from(udp_socket: crate::net::UdpSocket) -> OwnedSocket {
+        unsafe { OwnedSocket::from_raw_socket(udp_socket.into_raw_socket()) }
+    }
+}
+
+impl From<OwnedSocket> for crate::net::UdpSocket {
+    #[inline]
+    fn from(owned: OwnedSocket) -> Self {
+        unsafe { Self::from_raw_socket(owned.into_raw_socket()) }
+    }
+}
diff --git a/library/std/src/os/windows/mod.rs b/library/std/src/os/windows/mod.rs
index 52ac508f9f7..969054dd3b3 100644
--- a/library/std/src/os/windows/mod.rs
+++ b/library/std/src/os/windows/mod.rs
@@ -32,8 +32,11 @@ pub mod prelude {
     pub use super::fs::{MetadataExt, OpenOptionsExt};
     #[doc(no_inline)]
     #[stable(feature = "rust1", since = "1.0.0")]
-    pub use super::io::{AsRawHandle, AsRawSocket, RawHandle, RawSocket};
+    pub use super::io::{
+        AsHandle, AsSocket, BorrowedHandle, BorrowedSocket, FromRawHandle, FromRawSocket,
+        HandleOrInvalid, IntoRawHandle, IntoRawSocket, OwnedHandle, OwnedSocket,
+    };
     #[doc(no_inline)]
     #[stable(feature = "rust1", since = "1.0.0")]
-    pub use super::io::{FromRawHandle, FromRawSocket, IntoRawHandle, IntoRawSocket};
+    pub use super::io::{AsRawHandle, AsRawSocket, RawHandle, RawSocket};
 }
diff --git a/library/std/src/os/windows/process.rs b/library/std/src/os/windows/process.rs
index 9e7ccd015b6..b246599dfc0 100644
--- a/library/std/src/os/windows/process.rs
+++ b/library/std/src/os/windows/process.rs
@@ -3,7 +3,9 @@
 #![stable(feature = "process_extensions", since = "1.2.0")]
 
 use crate::ffi::OsStr;
-use crate::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle};
+use crate::os::windows::io::{
+    AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, IntoRawHandle, OwnedHandle, RawHandle,
+};
 use crate::process;
 use crate::sealed::Sealed;
 use crate::sys;
@@ -12,7 +14,16 @@ use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner};
 #[stable(feature = "process_extensions", since = "1.2.0")]
 impl FromRawHandle for process::Stdio {
     unsafe fn from_raw_handle(handle: RawHandle) -> process::Stdio {
-        let handle = sys::handle::Handle::new(handle as *mut _);
+        let handle = sys::handle::Handle::from_raw_handle(handle as *mut _);
+        let io = sys::process::Stdio::Handle(handle);
+        process::Stdio::from_inner(io)
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl From<OwnedHandle> for process::Stdio {
+    fn from(handle: OwnedHandle) -> process::Stdio {
+        let handle = sys::handle::Handle::from_inner(handle);
         let io = sys::process::Stdio::Handle(handle);
         process::Stdio::from_inner(io)
     }
@@ -22,14 +33,29 @@ impl FromRawHandle for process::Stdio {
 impl AsRawHandle for process::Child {
     #[inline]
     fn as_raw_handle(&self) -> RawHandle {
-        self.as_inner().handle().raw() as *mut _
+        self.as_inner().handle().as_raw_handle() as *mut _
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl AsHandle for process::Child {
+    #[inline]
+    fn as_handle(&self) -> BorrowedHandle<'_> {
+        self.as_inner().handle().as_handle()
     }
 }
 
 #[stable(feature = "into_raw_os", since = "1.4.0")]
 impl IntoRawHandle for process::Child {
     fn into_raw_handle(self) -> RawHandle {
-        self.into_inner().into_handle().into_raw() as *mut _
+        self.into_inner().into_handle().into_raw_handle() as *mut _
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl From<process::Child> for OwnedHandle {
+    fn from(child: process::Child) -> OwnedHandle {
+        child.into_inner().into_handle().into_inner()
     }
 }
 
@@ -37,7 +63,7 @@ impl IntoRawHandle for process::Child {
 impl AsRawHandle for process::ChildStdin {
     #[inline]
     fn as_raw_handle(&self) -> RawHandle {
-        self.as_inner().handle().raw() as *mut _
+        self.as_inner().handle().as_raw_handle() as *mut _
     }
 }
 
@@ -45,7 +71,7 @@ impl AsRawHandle for process::ChildStdin {
 impl AsRawHandle for process::ChildStdout {
     #[inline]
     fn as_raw_handle(&self) -> RawHandle {
-        self.as_inner().handle().raw() as *mut _
+        self.as_inner().handle().as_raw_handle() as *mut _
     }
 }
 
@@ -53,28 +79,28 @@ impl AsRawHandle for process::ChildStdout {
 impl AsRawHandle for process::ChildStderr {
     #[inline]
     fn as_raw_handle(&self) -> RawHandle {
-        self.as_inner().handle().raw() as *mut _
+        self.as_inner().handle().as_raw_handle() as *mut _
     }
 }
 
 #[stable(feature = "into_raw_os", since = "1.4.0")]
 impl IntoRawHandle for process::ChildStdin {
     fn into_raw_handle(self) -> RawHandle {
-        self.into_inner().into_handle().into_raw() as *mut _
+        self.into_inner().into_handle().into_raw_handle() as *mut _
     }
 }
 
 #[stable(feature = "into_raw_os", since = "1.4.0")]
 impl IntoRawHandle for process::ChildStdout {
     fn into_raw_handle(self) -> RawHandle {
-        self.into_inner().into_handle().into_raw() as *mut _
+        self.into_inner().into_handle().into_raw_handle() as *mut _
     }
 }
 
 #[stable(feature = "into_raw_os", since = "1.4.0")]
 impl IntoRawHandle for process::ChildStderr {
     fn into_raw_handle(self) -> RawHandle {
-        self.into_inner().into_handle().into_raw() as *mut _
+        self.into_inner().into_handle().into_raw_handle() as *mut _
     }
 }
 
diff --git a/library/std/src/os/windows/thread.rs b/library/std/src/os/windows/thread.rs
index 6bd02054f71..fb1bf66ceed 100644
--- a/library/std/src/os/windows/thread.rs
+++ b/library/std/src/os/windows/thread.rs
@@ -10,7 +10,7 @@ use crate::thread;
 impl<T> AsRawHandle for thread::JoinHandle<T> {
     #[inline]
     fn as_raw_handle(&self) -> RawHandle {
-        self.as_inner().handle().raw() as *mut _
+        self.as_inner().handle().as_raw_handle() as *mut _
     }
 }
 
@@ -18,6 +18,6 @@ impl<T> AsRawHandle for thread::JoinHandle<T> {
 impl<T> IntoRawHandle for thread::JoinHandle<T> {
     #[inline]
     fn into_raw_handle(self) -> RawHandle {
-        self.into_inner().into_handle().into_raw() as *mut _
+        self.into_inner().into_handle().into_raw_handle() as *mut _
     }
 }
diff --git a/library/std/src/process.rs b/library/std/src/process.rs
index f5ce5210f81..c9b21fcf9c6 100644
--- a/library/std/src/process.rs
+++ b/library/std/src/process.rs
@@ -258,6 +258,12 @@ pub struct ChildStdin {
     inner: AnonPipe,
 }
 
+// In addition to the `impl`s here, `ChildStdin` also has `impl`s for
+// `AsFd`/`From<OwnedFd>`/`Into<OwnedFd>` and
+// `AsRawFd`/`IntoRawFd`/`FromRawFd`, on Unix and WASI, and
+// `AsHandle`/`From<OwnedHandle>`/`Into<OwnedHandle>` and
+// `AsRawHandle`/`IntoRawHandle`/`FromRawHandle` on Windows.
+
 #[stable(feature = "process", since = "1.0.0")]
 impl Write for ChildStdin {
     fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
@@ -335,6 +341,12 @@ pub struct ChildStdout {
     inner: AnonPipe,
 }
 
+// In addition to the `impl`s here, `ChildStdout` also has `impl`s for
+// `AsFd`/`From<OwnedFd>`/`Into<OwnedFd>` and
+// `AsRawFd`/`IntoRawFd`/`FromRawFd`, on Unix and WASI, and
+// `AsHandle`/`From<OwnedHandle>`/`Into<OwnedHandle>` and
+// `AsRawHandle`/`IntoRawHandle`/`FromRawHandle` on Windows.
+
 #[stable(feature = "process", since = "1.0.0")]
 impl Read for ChildStdout {
     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
@@ -396,6 +408,12 @@ pub struct ChildStderr {
     inner: AnonPipe,
 }
 
+// In addition to the `impl`s here, `ChildStderr` also has `impl`s for
+// `AsFd`/`From<OwnedFd>`/`Into<OwnedFd>` and
+// `AsRawFd`/`IntoRawFd`/`FromRawFd`, on Unix and WASI, and
+// `AsHandle`/`From<OwnedHandle>`/`Into<OwnedHandle>` and
+// `AsRawHandle`/`IntoRawHandle`/`FromRawHandle` on Windows.
+
 #[stable(feature = "process", since = "1.0.0")]
 impl Read for ChildStderr {
     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
diff --git a/library/std/src/sys/unix/fd.rs b/library/std/src/sys/unix/fd.rs
index 28e32681e15..0956726084e 100644
--- a/library/std/src/sys/unix/fd.rs
+++ b/library/std/src/sys/unix/fd.rs
@@ -5,21 +5,14 @@ mod tests;
 
 use crate::cmp;
 use crate::io::{self, Initializer, IoSlice, IoSliceMut, Read};
-use crate::mem;
+use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
 use crate::sys::cvt;
-use crate::sys_common::AsInner;
+use crate::sys_common::{AsInner, FromInner, IntoInner};
 
 use libc::{c_int, c_void};
 
 #[derive(Debug)]
-#[rustc_layout_scalar_valid_range_start(0)]
-// libstd/os/raw/mod.rs assures me that every libstd-supported platform has a
-// 32-bit c_int. Below is -2, in two's complement, but that only works out
-// because c_int is 32 bits.
-#[rustc_layout_scalar_valid_range_end(0xFF_FF_FF_FE)]
-pub struct FileDesc {
-    fd: c_int,
-}
+pub struct FileDesc(OwnedFd);
 
 // The maximum read limit on most POSIX-like systems is `SSIZE_MAX`,
 // with the man page quoting that if the count of bytes to read is
@@ -67,26 +60,13 @@ const fn max_iov() -> usize {
 }
 
 impl FileDesc {
-    pub fn new(fd: c_int) -> FileDesc {
-        assert_ne!(fd, -1i32);
-        // SAFETY: we just asserted that the value is in the valid range and isn't `-1` (the only value bigger than `0xFF_FF_FF_FE` unsigned)
-        unsafe { FileDesc { fd } }
-    }
-
-    pub fn raw(&self) -> c_int {
-        self.fd
-    }
-
-    /// Extracts the actual file descriptor without closing it.
-    pub fn into_raw(self) -> c_int {
-        let fd = self.fd;
-        mem::forget(self);
-        fd
-    }
-
     pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
         let ret = cvt(unsafe {
-            libc::read(self.fd, buf.as_mut_ptr() as *mut c_void, cmp::min(buf.len(), READ_LIMIT))
+            libc::read(
+                self.as_raw_fd(),
+                buf.as_mut_ptr() as *mut c_void,
+                cmp::min(buf.len(), READ_LIMIT),
+            )
         })?;
         Ok(ret as usize)
     }
@@ -95,7 +75,7 @@ impl FileDesc {
     pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
         let ret = cvt(unsafe {
             libc::readv(
-                self.fd,
+                self.as_raw_fd(),
                 bufs.as_ptr() as *const libc::iovec,
                 cmp::min(bufs.len(), max_iov()) as c_int,
             )
@@ -138,7 +118,7 @@ impl FileDesc {
 
         unsafe {
             cvt_pread64(
-                self.fd,
+                self.as_raw_fd(),
                 buf.as_mut_ptr() as *mut c_void,
                 cmp::min(buf.len(), READ_LIMIT),
                 offset as i64,
@@ -149,7 +129,11 @@ impl FileDesc {
 
     pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
         let ret = cvt(unsafe {
-            libc::write(self.fd, buf.as_ptr() as *const c_void, cmp::min(buf.len(), READ_LIMIT))
+            libc::write(
+                self.as_raw_fd(),
+                buf.as_ptr() as *const c_void,
+                cmp::min(buf.len(), READ_LIMIT),
+            )
         })?;
         Ok(ret as usize)
     }
@@ -158,7 +142,7 @@ impl FileDesc {
     pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
         let ret = cvt(unsafe {
             libc::writev(
-                self.fd,
+                self.as_raw_fd(),
                 bufs.as_ptr() as *const libc::iovec,
                 cmp::min(bufs.len(), max_iov()) as c_int,
             )
@@ -196,7 +180,7 @@ impl FileDesc {
 
         unsafe {
             cvt_pwrite64(
-                self.fd,
+                self.as_raw_fd(),
                 buf.as_ptr() as *const c_void,
                 cmp::min(buf.len(), READ_LIMIT),
                 offset as i64,
@@ -207,7 +191,7 @@ impl FileDesc {
 
     #[cfg(target_os = "linux")]
     pub fn get_cloexec(&self) -> io::Result<bool> {
-        unsafe { Ok((cvt(libc::fcntl(self.fd, libc::F_GETFD))? & libc::FD_CLOEXEC) != 0) }
+        unsafe { Ok((cvt(libc::fcntl(self.as_raw_fd(), libc::F_GETFD))? & libc::FD_CLOEXEC) != 0) }
     }
 
     #[cfg(not(any(
@@ -224,7 +208,7 @@ impl FileDesc {
     )))]
     pub fn set_cloexec(&self) -> io::Result<()> {
         unsafe {
-            cvt(libc::ioctl(self.fd, libc::FIOCLEX))?;
+            cvt(libc::ioctl(self.as_raw_fd(), libc::FIOCLEX))?;
             Ok(())
         }
     }
@@ -242,10 +226,10 @@ impl FileDesc {
     ))]
     pub fn set_cloexec(&self) -> io::Result<()> {
         unsafe {
-            let previous = cvt(libc::fcntl(self.fd, libc::F_GETFD))?;
+            let previous = cvt(libc::fcntl(self.as_raw_fd(), libc::F_GETFD))?;
             let new = previous | libc::FD_CLOEXEC;
             if new != previous {
-                cvt(libc::fcntl(self.fd, libc::F_SETFD, new))?;
+                cvt(libc::fcntl(self.as_raw_fd(), libc::F_SETFD, new))?;
             }
             Ok(())
         }
@@ -261,7 +245,7 @@ impl FileDesc {
     pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
         unsafe {
             let v = nonblocking as c_int;
-            cvt(libc::ioctl(self.fd, libc::FIONBIO, &v))?;
+            cvt(libc::ioctl(self.as_raw_fd(), libc::FIONBIO, &v))?;
             Ok(())
         }
     }
@@ -269,14 +253,14 @@ impl FileDesc {
     #[cfg(not(target_os = "linux"))]
     pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
         unsafe {
-            let previous = cvt(libc::fcntl(self.fd, libc::F_GETFL))?;
+            let previous = cvt(libc::fcntl(self.as_raw_fd(), libc::F_GETFL))?;
             let new = if nonblocking {
                 previous | libc::O_NONBLOCK
             } else {
                 previous & !libc::O_NONBLOCK
             };
             if new != previous {
-                cvt(libc::fcntl(self.fd, libc::F_SETFL, new))?;
+                cvt(libc::fcntl(self.as_raw_fd(), libc::F_SETFL, new))?;
             }
             Ok(())
         }
@@ -296,8 +280,8 @@ impl FileDesc {
         #[cfg(target_os = "espidf")]
         let cmd = libc::F_DUPFD;
 
-        let fd = cvt(unsafe { libc::fcntl(self.raw(), cmd, 0) })?;
-        Ok(FileDesc::new(fd))
+        let fd = cvt(unsafe { libc::fcntl(self.as_raw_fd(), cmd, 0) })?;
+        Ok(unsafe { FileDesc::from_raw_fd(fd) })
     }
 }
 
@@ -312,19 +296,44 @@ impl<'a> Read for &'a FileDesc {
     }
 }
 
-impl AsInner<c_int> for FileDesc {
-    fn as_inner(&self) -> &c_int {
-        &self.fd
+impl AsInner<OwnedFd> for FileDesc {
+    fn as_inner(&self) -> &OwnedFd {
+        &self.0
+    }
+}
+
+impl IntoInner<OwnedFd> for FileDesc {
+    fn into_inner(self) -> OwnedFd {
+        self.0
+    }
+}
+
+impl FromInner<OwnedFd> for FileDesc {
+    fn from_inner(owned_fd: OwnedFd) -> Self {
+        Self(owned_fd)
+    }
+}
+
+impl AsFd for FileDesc {
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.0.as_fd()
+    }
+}
+
+impl AsRawFd for FileDesc {
+    fn as_raw_fd(&self) -> RawFd {
+        self.0.as_raw_fd()
+    }
+}
+
+impl IntoRawFd for FileDesc {
+    fn into_raw_fd(self) -> RawFd {
+        self.0.into_raw_fd()
     }
 }
 
-impl Drop for FileDesc {
-    fn drop(&mut self) {
-        // Note that errors are ignored when closing a file descriptor. The
-        // reason for this is that if an error occurs we don't actually know if
-        // the file descriptor was closed or not, and if we retried (for
-        // something like EINTR), we might close another valid file descriptor
-        // opened after we closed ours.
-        let _ = unsafe { libc::close(self.fd) };
+impl FromRawFd for FileDesc {
+    unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
+        Self(FromRawFd::from_raw_fd(raw_fd))
     }
 }
diff --git a/library/std/src/sys/unix/fd/tests.rs b/library/std/src/sys/unix/fd/tests.rs
index c9520485c3c..5d17e46786c 100644
--- a/library/std/src/sys/unix/fd/tests.rs
+++ b/library/std/src/sys/unix/fd/tests.rs
@@ -1,9 +1,10 @@
 use super::{FileDesc, IoSlice};
+use crate::os::unix::io::FromRawFd;
 use core::mem::ManuallyDrop;
 
 #[test]
 fn limit_vector_count() {
-    let stdout = ManuallyDrop::new(unsafe { FileDesc { fd: 1 } });
+    let stdout = ManuallyDrop::new(unsafe { FileDesc::from_raw_fd(1) });
     let bufs = (0..1500).map(|_| IoSlice::new(&[])).collect::<Vec<_>>();
     assert!(stdout.write_vectored(&bufs).is_ok());
 }
diff --git a/library/std/src/sys/unix/fs.rs b/library/std/src/sys/unix/fs.rs
index fd4defd72eb..6075eb5c7c5 100644
--- a/library/std/src/sys/unix/fs.rs
+++ b/library/std/src/sys/unix/fs.rs
@@ -4,13 +4,14 @@ use crate::ffi::{CStr, CString, OsStr, OsString};
 use crate::fmt;
 use crate::io::{self, Error, IoSlice, IoSliceMut, SeekFrom};
 use crate::mem;
+use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd};
 use crate::path::{Path, PathBuf};
 use crate::ptr;
 use crate::sync::Arc;
 use crate::sys::fd::FileDesc;
 use crate::sys::time::SystemTime;
 use crate::sys::{cvt, cvt_r};
-use crate::sys_common::{AsInner, FromInner};
+use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner};
 
 #[cfg(any(
     all(target_os = "linux", target_env = "gnu"),
@@ -764,11 +765,11 @@ impl File {
         // However, since this is a variadic function, C integer promotion rules mean that on
         // the ABI level, this still gets passed as `c_int` (aka `u32` on Unix platforms).
         let fd = cvt_r(|| unsafe { open64(path.as_ptr(), flags, opts.mode as c_int) })?;
-        Ok(File(FileDesc::new(fd)))
+        Ok(File(unsafe { FileDesc::from_raw_fd(fd) }))
     }
 
     pub fn file_attr(&self) -> io::Result<FileAttr> {
-        let fd = self.0.raw();
+        let fd = self.as_raw_fd();
 
         cfg_has_statx! {
             if let Some(ret) = unsafe { try_statx(
@@ -787,7 +788,7 @@ impl File {
     }
 
     pub fn fsync(&self) -> io::Result<()> {
-        cvt_r(|| unsafe { os_fsync(self.0.raw()) })?;
+        cvt_r(|| unsafe { os_fsync(self.as_raw_fd()) })?;
         return Ok(());
 
         #[cfg(any(target_os = "macos", target_os = "ios"))]
@@ -801,7 +802,7 @@ impl File {
     }
 
     pub fn datasync(&self) -> io::Result<()> {
-        cvt_r(|| unsafe { os_datasync(self.0.raw()) })?;
+        cvt_r(|| unsafe { os_datasync(self.as_raw_fd()) })?;
         return Ok(());
 
         #[cfg(any(target_os = "macos", target_os = "ios"))]
@@ -834,14 +835,14 @@ impl File {
 
     pub fn truncate(&self, size: u64) -> io::Result<()> {
         #[cfg(target_os = "android")]
-        return crate::sys::android::ftruncate64(self.0.raw(), size);
+        return crate::sys::android::ftruncate64(self.as_raw_fd(), size);
 
         #[cfg(not(target_os = "android"))]
         {
             use crate::convert::TryInto;
             let size: off64_t =
                 size.try_into().map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
-            cvt_r(|| unsafe { ftruncate64(self.0.raw(), size) }).map(drop)
+            cvt_r(|| unsafe { ftruncate64(self.as_raw_fd(), size) }).map(drop)
         }
     }
 
@@ -891,7 +892,7 @@ impl File {
             SeekFrom::End(off) => (libc::SEEK_END, off),
             SeekFrom::Current(off) => (libc::SEEK_CUR, off),
         };
-        let n = cvt(unsafe { lseek64(self.0.raw(), pos, whence) })?;
+        let n = cvt(unsafe { lseek64(self.as_raw_fd(), pos, whence) })?;
         Ok(n as u64)
     }
 
@@ -899,16 +900,8 @@ impl File {
         self.0.duplicate().map(File)
     }
 
-    pub fn fd(&self) -> &FileDesc {
-        &self.0
-    }
-
-    pub fn into_fd(self) -> FileDesc {
-        self.0
-    }
-
     pub fn set_permissions(&self, perm: FilePermissions) -> io::Result<()> {
-        cvt_r(|| unsafe { libc::fchmod(self.0.raw(), perm.mode) })?;
+        cvt_r(|| unsafe { libc::fchmod(self.as_raw_fd(), perm.mode) })?;
         Ok(())
     }
 }
@@ -933,9 +926,51 @@ fn cstr(path: &Path) -> io::Result<CString> {
     Ok(CString::new(path.as_os_str().as_bytes())?)
 }
 
-impl FromInner<c_int> for File {
-    fn from_inner(fd: c_int) -> File {
-        File(FileDesc::new(fd))
+impl AsInner<FileDesc> for File {
+    fn as_inner(&self) -> &FileDesc {
+        &self.0
+    }
+}
+
+impl AsInnerMut<FileDesc> for File {
+    fn as_inner_mut(&mut self) -> &mut FileDesc {
+        &mut self.0
+    }
+}
+
+impl IntoInner<FileDesc> for File {
+    fn into_inner(self) -> FileDesc {
+        self.0
+    }
+}
+
+impl FromInner<FileDesc> for File {
+    fn from_inner(file_desc: FileDesc) -> Self {
+        Self(file_desc)
+    }
+}
+
+impl AsFd for File {
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.0.as_fd()
+    }
+}
+
+impl AsRawFd for File {
+    fn as_raw_fd(&self) -> RawFd {
+        self.0.as_raw_fd()
+    }
+}
+
+impl IntoRawFd for File {
+    fn into_raw_fd(self) -> RawFd {
+        self.0.into_raw_fd()
+    }
+}
+
+impl FromRawFd for File {
+    unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
+        Self(FromRawFd::from_raw_fd(raw_fd))
     }
 }
 
@@ -1009,7 +1044,7 @@ impl fmt::Debug for File {
             None
         }
 
-        let fd = self.0.raw();
+        let fd = self.as_raw_fd();
         let mut b = f.debug_struct("File");
         b.field("fd", &fd);
         if let Some(path) = get_path(fd) {
diff --git a/library/std/src/sys/unix/net.rs b/library/std/src/sys/unix/net.rs
index 3f614fde08a..c2f5da1dbbb 100644
--- a/library/std/src/sys/unix/net.rs
+++ b/library/std/src/sys/unix/net.rs
@@ -3,6 +3,7 @@ use crate::ffi::CStr;
 use crate::io::{self, IoSlice, IoSliceMut};
 use crate::mem;
 use crate::net::{Shutdown, SocketAddr};
+use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd};
 use crate::str;
 use crate::sys::fd::FileDesc;
 use crate::sys_common::net::{getsockopt, setsockopt, sockaddr_to_addr};
@@ -74,10 +75,10 @@ impl Socket {
                     // flag to atomically create the socket and set it as
                     // CLOEXEC. On Linux this was added in 2.6.27.
                     let fd = cvt(libc::socket(fam, ty | libc::SOCK_CLOEXEC, 0))?;
-                    Ok(Socket(FileDesc::new(fd)))
+                    Ok(Socket(FileDesc::from_raw_fd(fd)))
                 } else {
                     let fd = cvt(libc::socket(fam, ty, 0))?;
-                    let fd = FileDesc::new(fd);
+                    let fd = FileDesc::from_raw_fd(fd);
                     fd.set_cloexec()?;
                     let socket = Socket(fd);
 
@@ -109,11 +110,11 @@ impl Socket {
                 ))] {
                     // Like above, set cloexec atomically
                     cvt(libc::socketpair(fam, ty | libc::SOCK_CLOEXEC, 0, fds.as_mut_ptr()))?;
-                    Ok((Socket(FileDesc::new(fds[0])), Socket(FileDesc::new(fds[1]))))
+                    Ok((Socket(FileDesc::from_raw_fd(fds[0])), Socket(FileDesc::from_raw_fd(fds[1]))))
                 } else {
                     cvt(libc::socketpair(fam, ty, 0, fds.as_mut_ptr()))?;
-                    let a = FileDesc::new(fds[0]);
-                    let b = FileDesc::new(fds[1]);
+                    let a = FileDesc::from_raw_fd(fds[0]);
+                    let b = FileDesc::from_raw_fd(fds[1]);
                     a.set_cloexec()?;
                     b.set_cloexec()?;
                     Ok((Socket(a), Socket(b)))
@@ -131,7 +132,7 @@ impl Socket {
         self.set_nonblocking(true)?;
         let r = unsafe {
             let (addrp, len) = addr.into_inner();
-            cvt(libc::connect(self.0.raw(), addrp, len))
+            cvt(libc::connect(self.as_raw_fd(), addrp, len))
         };
         self.set_nonblocking(false)?;
 
@@ -142,7 +143,7 @@ impl Socket {
             Err(e) => return Err(e),
         }
 
-        let mut pollfd = libc::pollfd { fd: self.0.raw(), events: libc::POLLOUT, revents: 0 };
+        let mut pollfd = libc::pollfd { fd: self.as_raw_fd(), events: libc::POLLOUT, revents: 0 };
 
         if timeout.as_secs() == 0 && timeout.subsec_nanos() == 0 {
             return Err(io::Error::new_const(
@@ -212,15 +213,17 @@ impl Socket {
                 target_os = "netbsd",
                 target_os = "openbsd",
             ))] {
-                let fd = cvt_r(|| unsafe {
-                    libc::accept4(self.0.raw(), storage, len, libc::SOCK_CLOEXEC)
-                })?;
-                Ok(Socket(FileDesc::new(fd)))
+                unsafe {
+                    let fd = cvt_r(|| libc::accept4(self.as_raw_fd(), storage, len, libc::SOCK_CLOEXEC))?;
+                    Ok(Socket(FileDesc::from_raw_fd(fd)))
+                }
             } else {
-                let fd = cvt_r(|| unsafe { libc::accept(self.0.raw(), storage, len) })?;
-                let fd = FileDesc::new(fd);
-                fd.set_cloexec()?;
-                Ok(Socket(fd))
+                unsafe {
+                    let fd = cvt_r(|| libc::accept(self.as_raw_fd(), storage, len))?;
+                    let fd = FileDesc::from_raw_fd(fd);
+                    fd.set_cloexec()?;
+                    Ok(Socket(fd))
+                }
             }
         }
     }
@@ -231,7 +234,7 @@ impl Socket {
 
     fn recv_with_flags(&self, buf: &mut [u8], flags: c_int) -> io::Result<usize> {
         let ret = cvt(unsafe {
-            libc::recv(self.0.raw(), buf.as_mut_ptr() as *mut c_void, buf.len(), flags)
+            libc::recv(self.as_raw_fd(), buf.as_mut_ptr() as *mut c_void, buf.len(), flags)
         })?;
         Ok(ret as usize)
     }
@@ -263,7 +266,7 @@ impl Socket {
 
         let n = cvt(unsafe {
             libc::recvfrom(
-                self.0.raw(),
+                self.as_raw_fd(),
                 buf.as_mut_ptr() as *mut c_void,
                 buf.len(),
                 flags,
@@ -288,7 +291,7 @@ impl Socket {
         target_os = "openbsd",
     ))]
     pub fn recv_msg(&self, msg: &mut libc::msghdr) -> io::Result<usize> {
-        let n = cvt(unsafe { libc::recvmsg(self.0.raw(), msg, libc::MSG_CMSG_CLOEXEC) })?;
+        let n = cvt(unsafe { libc::recvmsg(self.as_raw_fd(), msg, libc::MSG_CMSG_CLOEXEC) })?;
         Ok(n as usize)
     }
 
@@ -319,7 +322,7 @@ impl Socket {
         target_os = "openbsd",
     ))]
     pub fn send_msg(&self, msg: &mut libc::msghdr) -> io::Result<usize> {
-        let n = cvt(unsafe { libc::sendmsg(self.0.raw(), msg, 0) })?;
+        let n = cvt(unsafe { libc::sendmsg(self.as_raw_fd(), msg, 0) })?;
         Ok(n as usize)
     }
 
@@ -369,7 +372,7 @@ impl Socket {
             Shutdown::Read => libc::SHUT_RD,
             Shutdown::Both => libc::SHUT_RDWR,
         };
-        cvt(unsafe { libc::shutdown(self.0.raw(), how) })?;
+        cvt(unsafe { libc::shutdown(self.as_raw_fd(), how) })?;
         Ok(())
     }
 
@@ -396,7 +399,7 @@ impl Socket {
     #[cfg(not(any(target_os = "solaris", target_os = "illumos")))]
     pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
         let mut nonblocking = nonblocking as libc::c_int;
-        cvt(unsafe { libc::ioctl(*self.as_inner(), libc::FIONBIO, &mut nonblocking) }).map(drop)
+        cvt(unsafe { libc::ioctl(self.as_raw_fd(), libc::FIONBIO, &mut nonblocking) }).map(drop)
     }
 
     #[cfg(any(target_os = "solaris", target_os = "illumos"))]
@@ -410,23 +413,52 @@ impl Socket {
         let raw: c_int = getsockopt(self, libc::SOL_SOCKET, libc::SO_ERROR)?;
         if raw == 0 { Ok(None) } else { Ok(Some(io::Error::from_raw_os_error(raw as i32))) }
     }
+
+    // This is used by sys_common code to abstract over Windows and Unix.
+    pub fn as_raw(&self) -> RawFd {
+        self.as_raw_fd()
+    }
+}
+
+impl AsInner<FileDesc> for Socket {
+    fn as_inner(&self) -> &FileDesc {
+        &self.0
+    }
+}
+
+impl IntoInner<FileDesc> for Socket {
+    fn into_inner(self) -> FileDesc {
+        self.0
+    }
+}
+
+impl FromInner<FileDesc> for Socket {
+    fn from_inner(file_desc: FileDesc) -> Self {
+        Self(file_desc)
+    }
+}
+
+impl AsFd for Socket {
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.0.as_fd()
+    }
 }
 
-impl AsInner<c_int> for Socket {
-    fn as_inner(&self) -> &c_int {
-        self.0.as_inner()
+impl AsRawFd for Socket {
+    fn as_raw_fd(&self) -> RawFd {
+        self.0.as_raw_fd()
     }
 }
 
-impl FromInner<c_int> for Socket {
-    fn from_inner(fd: c_int) -> Socket {
-        Socket(FileDesc::new(fd))
+impl IntoRawFd for Socket {
+    fn into_raw_fd(self) -> RawFd {
+        self.0.into_raw_fd()
     }
 }
 
-impl IntoInner<c_int> for Socket {
-    fn into_inner(self) -> c_int {
-        self.0.into_raw()
+impl FromRawFd for Socket {
+    unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
+        Self(FromRawFd::from_raw_fd(raw_fd))
     }
 }
 
diff --git a/library/std/src/sys/unix/pipe.rs b/library/std/src/sys/unix/pipe.rs
index 7ae37bdda70..a56c275c942 100644
--- a/library/std/src/sys/unix/pipe.rs
+++ b/library/std/src/sys/unix/pipe.rs
@@ -1,7 +1,9 @@
 use crate::io::{self, IoSlice, IoSliceMut};
 use crate::mem;
+use crate::os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd};
 use crate::sys::fd::FileDesc;
 use crate::sys::{cvt, cvt_r};
+use crate::sys_common::IntoInner;
 
 ////////////////////////////////////////////////////////////////////////////////
 // Anonymous pipes
@@ -24,16 +26,20 @@ pub fn anon_pipe() -> io::Result<(AnonPipe, AnonPipe)> {
             target_os = "openbsd",
             target_os = "redox"
         ))] {
-            cvt(unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC) })?;
-            Ok((AnonPipe(FileDesc::new(fds[0])), AnonPipe(FileDesc::new(fds[1]))))
+            unsafe {
+                cvt(libc::pipe2(fds.as_mut_ptr(), libc::O_CLOEXEC))?;
+                Ok((AnonPipe(FileDesc::from_raw_fd(fds[0])), AnonPipe(FileDesc::from_raw_fd(fds[1]))))
+            }
         } else {
-            cvt(unsafe { libc::pipe(fds.as_mut_ptr()) })?;
-
-            let fd0 = FileDesc::new(fds[0]);
-            let fd1 = FileDesc::new(fds[1]);
-            fd0.set_cloexec()?;
-            fd1.set_cloexec()?;
-            Ok((AnonPipe(fd0), AnonPipe(fd1)))
+            unsafe {
+                cvt(libc::pipe(fds.as_mut_ptr()))?;
+
+                let fd0 = FileDesc::from_raw_fd(fds[0]);
+                let fd1 = FileDesc::from_raw_fd(fds[1]);
+                fd0.set_cloexec()?;
+                fd1.set_cloexec()?;
+                Ok((AnonPipe(fd0), AnonPipe(fd1)))
+            }
         }
     }
 }
@@ -64,11 +70,10 @@ impl AnonPipe {
     pub fn is_write_vectored(&self) -> bool {
         self.0.is_write_vectored()
     }
+}
 
-    pub fn fd(&self) -> &FileDesc {
-        &self.0
-    }
-    pub fn into_fd(self) -> FileDesc {
+impl IntoInner<FileDesc> for AnonPipe {
+    fn into_inner(self) -> FileDesc {
         self.0
     }
 }
@@ -76,15 +81,15 @@ impl AnonPipe {
 pub fn read2(p1: AnonPipe, v1: &mut Vec<u8>, p2: AnonPipe, v2: &mut Vec<u8>) -> io::Result<()> {
     // Set both pipes into nonblocking mode as we're gonna be reading from both
     // in the `select` loop below, and we wouldn't want one to block the other!
-    let p1 = p1.into_fd();
-    let p2 = p2.into_fd();
+    let p1 = p1.into_inner();
+    let p2 = p2.into_inner();
     p1.set_nonblocking(true)?;
     p2.set_nonblocking(true)?;
 
     let mut fds: [libc::pollfd; 2] = unsafe { mem::zeroed() };
-    fds[0].fd = p1.raw();
+    fds[0].fd = p1.as_raw_fd();
     fds[0].events = libc::POLLIN;
-    fds[1].fd = p2.raw();
+    fds[1].fd = p2.as_raw_fd();
     fds[1].events = libc::POLLIN;
     loop {
         // wait for either pipe to become readable using `poll`
@@ -120,3 +125,27 @@ pub fn read2(p1: AnonPipe, v1: &mut Vec<u8>, p2: AnonPipe, v2: &mut Vec<u8>) ->
         }
     }
 }
+
+impl AsRawFd for AnonPipe {
+    fn as_raw_fd(&self) -> RawFd {
+        self.0.as_raw_fd()
+    }
+}
+
+impl AsFd for AnonPipe {
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.0.as_fd()
+    }
+}
+
+impl IntoRawFd for AnonPipe {
+    fn into_raw_fd(self) -> RawFd {
+        self.0.into_raw_fd()
+    }
+}
+
+impl FromRawFd for AnonPipe {
+    unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
+        Self(FromRawFd::from_raw_fd(raw_fd))
+    }
+}
diff --git a/library/std/src/sys/unix/process/process_common.rs b/library/std/src/sys/unix/process/process_common.rs
index a1972380a9f..7b261a302c3 100644
--- a/library/std/src/sys/unix/process/process_common.rs
+++ b/library/std/src/sys/unix/process/process_common.rs
@@ -13,6 +13,7 @@ use crate::sys::fd::FileDesc;
 use crate::sys::fs::File;
 use crate::sys::pipe::{self, AnonPipe};
 use crate::sys_common::process::{CommandEnv, CommandEnvs};
+use crate::sys_common::IntoInner;
 
 #[cfg(not(target_os = "fuchsia"))]
 use crate::sys::fs::OpenOptions;
@@ -388,17 +389,17 @@ impl Stdio {
             // stderr. No matter which we dup first, the second will get
             // overwritten prematurely.
             Stdio::Fd(ref fd) => {
-                if fd.raw() >= 0 && fd.raw() <= libc::STDERR_FILENO {
+                if fd.as_raw_fd() >= 0 && fd.as_raw_fd() <= libc::STDERR_FILENO {
                     Ok((ChildStdio::Owned(fd.duplicate()?), None))
                 } else {
-                    Ok((ChildStdio::Explicit(fd.raw()), None))
+                    Ok((ChildStdio::Explicit(fd.as_raw_fd()), None))
                 }
             }
 
             Stdio::MakePipe => {
                 let (reader, writer) = pipe::anon_pipe()?;
                 let (ours, theirs) = if readable { (writer, reader) } else { (reader, writer) };
-                Ok((ChildStdio::Owned(theirs.into_fd()), Some(ours)))
+                Ok((ChildStdio::Owned(theirs.into_inner()), Some(ours)))
             }
 
             #[cfg(not(target_os = "fuchsia"))]
@@ -408,7 +409,7 @@ impl Stdio {
                 opts.write(!readable);
                 let path = unsafe { CStr::from_ptr(DEV_NULL.as_ptr() as *const _) };
                 let fd = File::open_c(&path, &opts)?;
-                Ok((ChildStdio::Owned(fd.into_fd()), None))
+                Ok((ChildStdio::Owned(fd.into_inner()), None))
             }
 
             #[cfg(target_os = "fuchsia")]
@@ -419,13 +420,13 @@ impl Stdio {
 
 impl From<AnonPipe> for Stdio {
     fn from(pipe: AnonPipe) -> Stdio {
-        Stdio::Fd(pipe.into_fd())
+        Stdio::Fd(pipe.into_inner())
     }
 }
 
 impl From<File> for Stdio {
     fn from(file: File) -> Stdio {
-        Stdio::Fd(file.into_fd())
+        Stdio::Fd(file.into_inner())
     }
 }
 
@@ -434,7 +435,7 @@ impl ChildStdio {
         match *self {
             ChildStdio::Inherit => None,
             ChildStdio::Explicit(fd) => Some(fd),
-            ChildStdio::Owned(ref fd) => Some(fd.raw()),
+            ChildStdio::Owned(ref fd) => Some(fd.as_raw_fd()),
 
             #[cfg(target_os = "fuchsia")]
             ChildStdio::Null => None,
diff --git a/library/std/src/sys/unix/process/process_unix.rs b/library/std/src/sys/unix/process/process_unix.rs
index 4b210d6af13..12edf04a4e2 100644
--- a/library/std/src/sys/unix/process/process_unix.rs
+++ b/library/std/src/sys/unix/process/process_unix.rs
@@ -97,7 +97,9 @@ impl Command {
         drop(env_lock);
         drop(output);
 
-        let mut p = Process::new(pid, pidfd);
+        // Safety: We obtained the pidfd from calling `clone3` with
+        // `CLONE_PIDFD` so it's valid an otherwise unowned.
+        let mut p = unsafe { Process::new(pid, pidfd) };
         let mut bytes = [0; 8];
 
         // loop to handle EINTR
@@ -446,7 +448,8 @@ impl Command {
             None => None,
         };
 
-        let mut p = Process::new(0, -1);
+        // Safety: -1 indicates we don't have a pidfd.
+        let mut p = unsafe { Process::new(0, -1) };
 
         struct PosixSpawnFileActions<'a>(&'a mut MaybeUninit<libc::posix_spawn_file_actions_t>);
 
@@ -545,14 +548,17 @@ pub struct Process {
 
 impl Process {
     #[cfg(target_os = "linux")]
-    fn new(pid: pid_t, pidfd: pid_t) -> Self {
+    unsafe fn new(pid: pid_t, pidfd: pid_t) -> Self {
+        use crate::os::unix::io::FromRawFd;
         use crate::sys_common::FromInner;
-        let pidfd = (pidfd >= 0).then(|| PidFd::from_inner(sys::fd::FileDesc::new(pidfd)));
+        // Safety: If `pidfd` is nonnegative, we assume it's valid and otherwise unowned.
+        let pidfd = (pidfd >= 0)
+            .then(|| PidFd::from_inner(unsafe { sys::fd::FileDesc::from_raw_fd(pidfd) }));
         Process { pid, status: None, pidfd }
     }
 
     #[cfg(not(target_os = "linux"))]
-    fn new(pid: pid_t, _pidfd: pid_t) -> Self {
+    unsafe fn new(pid: pid_t, _pidfd: pid_t) -> Self {
         Process { pid, status: None }
     }
 
diff --git a/library/std/src/sys/unix/stdio.rs b/library/std/src/sys/unix/stdio.rs
index a05fe8165cf..b359987595d 100644
--- a/library/std/src/sys/unix/stdio.rs
+++ b/library/std/src/sys/unix/stdio.rs
@@ -1,5 +1,6 @@
 use crate::io::{self, IoSlice, IoSliceMut};
 use crate::mem::ManuallyDrop;
+use crate::os::unix::io::{AsFd, BorrowedFd, FromRawFd};
 use crate::sys::fd::FileDesc;
 
 pub struct Stdin(());
@@ -14,11 +15,11 @@ impl Stdin {
 
 impl io::Read for Stdin {
     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
-        ManuallyDrop::new(FileDesc::new(libc::STDIN_FILENO)).read(buf)
+        unsafe { ManuallyDrop::new(FileDesc::from_raw_fd(libc::STDIN_FILENO)).read(buf) }
     }
 
     fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
-        ManuallyDrop::new(FileDesc::new(libc::STDIN_FILENO)).read_vectored(bufs)
+        unsafe { ManuallyDrop::new(FileDesc::from_raw_fd(libc::STDIN_FILENO)).read_vectored(bufs) }
     }
 
     #[inline]
@@ -35,11 +36,13 @@ impl Stdout {
 
 impl io::Write for Stdout {
     fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
-        ManuallyDrop::new(FileDesc::new(libc::STDOUT_FILENO)).write(buf)
+        unsafe { ManuallyDrop::new(FileDesc::from_raw_fd(libc::STDOUT_FILENO)).write(buf) }
     }
 
     fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
-        ManuallyDrop::new(FileDesc::new(libc::STDOUT_FILENO)).write_vectored(bufs)
+        unsafe {
+            ManuallyDrop::new(FileDesc::from_raw_fd(libc::STDOUT_FILENO)).write_vectored(bufs)
+        }
     }
 
     #[inline]
@@ -60,11 +63,13 @@ impl Stderr {
 
 impl io::Write for Stderr {
     fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
-        ManuallyDrop::new(FileDesc::new(libc::STDERR_FILENO)).write(buf)
+        unsafe { ManuallyDrop::new(FileDesc::from_raw_fd(libc::STDERR_FILENO)).write(buf) }
     }
 
     fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
-        ManuallyDrop::new(FileDesc::new(libc::STDERR_FILENO)).write_vectored(bufs)
+        unsafe {
+            ManuallyDrop::new(FileDesc::from_raw_fd(libc::STDERR_FILENO)).write_vectored(bufs)
+        }
     }
 
     #[inline]
@@ -86,3 +91,51 @@ pub const STDIN_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE;
 pub fn panic_output() -> Option<impl io::Write> {
     Some(Stderr::new())
 }
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl AsFd for io::Stdin {
+    #[inline]
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        unsafe { BorrowedFd::borrow_raw_fd(libc::STDIN_FILENO) }
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl<'a> AsFd for io::StdinLock<'a> {
+    #[inline]
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        unsafe { BorrowedFd::borrow_raw_fd(libc::STDIN_FILENO) }
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl AsFd for io::Stdout {
+    #[inline]
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        unsafe { BorrowedFd::borrow_raw_fd(libc::STDOUT_FILENO) }
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl<'a> AsFd for io::StdoutLock<'a> {
+    #[inline]
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        unsafe { BorrowedFd::borrow_raw_fd(libc::STDOUT_FILENO) }
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl AsFd for io::Stderr {
+    #[inline]
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        unsafe { BorrowedFd::borrow_raw_fd(libc::STDERR_FILENO) }
+    }
+}
+
+#[unstable(feature = "io_safety", issue = "87074")]
+impl<'a> AsFd for io::StderrLock<'a> {
+    #[inline]
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        unsafe { BorrowedFd::borrow_raw_fd(libc::STDERR_FILENO) }
+    }
+}
diff --git a/library/std/src/sys/wasi/fd.rs b/library/std/src/sys/wasi/fd.rs
index 1f6ea8d6e8d..e4f4456611c 100644
--- a/library/std/src/sys/wasi/fd.rs
+++ b/library/std/src/sys/wasi/fd.rs
@@ -5,11 +5,12 @@ use super::err2io;
 use crate::io::{self, IoSlice, IoSliceMut, SeekFrom};
 use crate::mem;
 use crate::net::Shutdown;
-use crate::os::raw::c_int;
+use crate::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
+use crate::sys_common::{AsInner, AsInnerMut, FromInner, IntoInner};
 
 #[derive(Debug)]
 pub struct WasiFd {
-    fd: c_int,
+    fd: OwnedFd,
 }
 
 fn iovec<'a>(a: &'a mut [IoSliceMut<'_>]) -> &'a [wasi::Iovec] {
@@ -27,38 +28,26 @@ fn ciovec<'a>(a: &'a [IoSlice<'_>]) -> &'a [wasi::Ciovec] {
 }
 
 impl WasiFd {
-    pub unsafe fn from_raw(fd: c_int) -> WasiFd {
-        WasiFd { fd }
-    }
-
-    pub fn into_raw(self) -> c_int {
-        let ret = self.fd;
-        mem::forget(self);
-        ret
-    }
-
-    pub fn as_raw(&self) -> c_int {
-        self.fd
-    }
-
     pub fn datasync(&self) -> io::Result<()> {
-        unsafe { wasi::fd_datasync(self.fd as wasi::Fd).map_err(err2io) }
+        unsafe { wasi::fd_datasync(self.as_raw_fd() as wasi::Fd).map_err(err2io) }
     }
 
     pub fn pread(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> io::Result<usize> {
-        unsafe { wasi::fd_pread(self.fd as wasi::Fd, iovec(bufs), offset).map_err(err2io) }
+        unsafe { wasi::fd_pread(self.as_raw_fd() as wasi::Fd, iovec(bufs), offset).map_err(err2io) }
     }
 
     pub fn pwrite(&self, bufs: &[IoSlice<'_>], offset: u64) -> io::Result<usize> {
-        unsafe { wasi::fd_pwrite(self.fd as wasi::Fd, ciovec(bufs), offset).map_err(err2io) }
+        unsafe {
+            wasi::fd_pwrite(self.as_raw_fd() as wasi::Fd, ciovec(bufs), offset).map_err(err2io)
+        }
     }
 
     pub fn read(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
-        unsafe { wasi::fd_read(self.fd as wasi::Fd, iovec(bufs)).map_err(err2io) }
+        unsafe { wasi::fd_read(self.as_raw_fd() as wasi::Fd, iovec(bufs)).map_err(err2io) }
     }
 
     pub fn write(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
-        unsafe { wasi::fd_write(self.fd as wasi::Fd, ciovec(bufs)).map_err(err2io) }
+        unsafe { wasi::fd_write(self.as_raw_fd() as wasi::Fd, ciovec(bufs)).map_err(err2io) }
     }
 
     pub fn seek(&self, pos: SeekFrom) -> io::Result<u64> {
@@ -67,37 +56,42 @@ impl WasiFd {
             SeekFrom::End(pos) => (wasi::WHENCE_END, pos),
             SeekFrom::Current(pos) => (wasi::WHENCE_CUR, pos),
         };
-        unsafe { wasi::fd_seek(self.fd as wasi::Fd, offset, whence).map_err(err2io) }
+        unsafe { wasi::fd_seek(self.as_raw_fd() as wasi::Fd, offset, whence).map_err(err2io) }
     }
 
     pub fn tell(&self) -> io::Result<u64> {
-        unsafe { wasi::fd_tell(self.fd as wasi::Fd).map_err(err2io) }
+        unsafe { wasi::fd_tell(self.as_raw_fd() as wasi::Fd).map_err(err2io) }
     }
 
     // FIXME: __wasi_fd_fdstat_get
 
     pub fn set_flags(&self, flags: wasi::Fdflags) -> io::Result<()> {
-        unsafe { wasi::fd_fdstat_set_flags(self.fd as wasi::Fd, flags).map_err(err2io) }
+        unsafe { wasi::fd_fdstat_set_flags(self.as_raw_fd() as wasi::Fd, flags).map_err(err2io) }
     }
 
     pub fn set_rights(&self, base: wasi::Rights, inheriting: wasi::Rights) -> io::Result<()> {
-        unsafe { wasi::fd_fdstat_set_rights(self.fd as wasi::Fd, base, inheriting).map_err(err2io) }
+        unsafe {
+            wasi::fd_fdstat_set_rights(self.as_raw_fd() as wasi::Fd, base, inheriting)
+                .map_err(err2io)
+        }
     }
 
     pub fn sync(&self) -> io::Result<()> {
-        unsafe { wasi::fd_sync(self.fd as wasi::Fd).map_err(err2io) }
+        unsafe { wasi::fd_sync(self.as_raw_fd() as wasi::Fd).map_err(err2io) }
     }
 
     pub fn advise(&self, offset: u64, len: u64, advice: wasi::Advice) -> io::Result<()> {
-        unsafe { wasi::fd_advise(self.fd as wasi::Fd, offset, len, advice).map_err(err2io) }
+        unsafe {
+            wasi::fd_advise(self.as_raw_fd() as wasi::Fd, offset, len, advice).map_err(err2io)
+        }
     }
 
     pub fn allocate(&self, offset: u64, len: u64) -> io::Result<()> {
-        unsafe { wasi::fd_allocate(self.fd as wasi::Fd, offset, len).map_err(err2io) }
+        unsafe { wasi::fd_allocate(self.as_raw_fd() as wasi::Fd, offset, len).map_err(err2io) }
     }
 
     pub fn create_directory(&self, path: &str) -> io::Result<()> {
-        unsafe { wasi::path_create_directory(self.fd as wasi::Fd, path).map_err(err2io) }
+        unsafe { wasi::path_create_directory(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) }
     }
 
     pub fn link(
@@ -109,10 +103,10 @@ impl WasiFd {
     ) -> io::Result<()> {
         unsafe {
             wasi::path_link(
-                self.fd as wasi::Fd,
+                self.as_raw_fd() as wasi::Fd,
                 old_flags,
                 old_path,
-                new_fd.fd as wasi::Fd,
+                new_fd.as_raw_fd() as wasi::Fd,
                 new_path,
             )
             .map_err(err2io)
@@ -130,7 +124,7 @@ impl WasiFd {
     ) -> io::Result<WasiFd> {
         unsafe {
             wasi::path_open(
-                self.fd as wasi::Fd,
+                self.as_raw_fd() as wasi::Fd,
                 dirflags,
                 path,
                 oflags,
@@ -138,34 +132,39 @@ impl WasiFd {
                 fs_rights_inheriting,
                 fs_flags,
             )
-            .map(|fd| WasiFd::from_raw(fd as c_int))
+            .map(|fd| WasiFd::from_raw_fd(fd as RawFd))
             .map_err(err2io)
         }
     }
 
     pub fn readdir(&self, buf: &mut [u8], cookie: wasi::Dircookie) -> io::Result<usize> {
         unsafe {
-            wasi::fd_readdir(self.fd as wasi::Fd, buf.as_mut_ptr(), buf.len(), cookie)
+            wasi::fd_readdir(self.as_raw_fd() as wasi::Fd, buf.as_mut_ptr(), buf.len(), cookie)
                 .map_err(err2io)
         }
     }
 
     pub fn readlink(&self, path: &str, buf: &mut [u8]) -> io::Result<usize> {
         unsafe {
-            wasi::path_readlink(self.fd as wasi::Fd, path, buf.as_mut_ptr(), buf.len())
+            wasi::path_readlink(self.as_raw_fd() as wasi::Fd, path, buf.as_mut_ptr(), buf.len())
                 .map_err(err2io)
         }
     }
 
     pub fn rename(&self, old_path: &str, new_fd: &WasiFd, new_path: &str) -> io::Result<()> {
         unsafe {
-            wasi::path_rename(self.fd as wasi::Fd, old_path, new_fd.fd as wasi::Fd, new_path)
-                .map_err(err2io)
+            wasi::path_rename(
+                self.as_raw_fd() as wasi::Fd,
+                old_path,
+                new_fd.as_raw_fd() as wasi::Fd,
+                new_path,
+            )
+            .map_err(err2io)
         }
     }
 
     pub fn filestat_get(&self) -> io::Result<wasi::Filestat> {
-        unsafe { wasi::fd_filestat_get(self.fd as wasi::Fd).map_err(err2io) }
+        unsafe { wasi::fd_filestat_get(self.as_raw_fd() as wasi::Fd).map_err(err2io) }
     }
 
     pub fn filestat_set_times(
@@ -175,12 +174,13 @@ impl WasiFd {
         fstflags: wasi::Fstflags,
     ) -> io::Result<()> {
         unsafe {
-            wasi::fd_filestat_set_times(self.fd as wasi::Fd, atim, mtim, fstflags).map_err(err2io)
+            wasi::fd_filestat_set_times(self.as_raw_fd() as wasi::Fd, atim, mtim, fstflags)
+                .map_err(err2io)
         }
     }
 
     pub fn filestat_set_size(&self, size: u64) -> io::Result<()> {
-        unsafe { wasi::fd_filestat_set_size(self.fd as wasi::Fd, size).map_err(err2io) }
+        unsafe { wasi::fd_filestat_set_size(self.as_raw_fd() as wasi::Fd, size).map_err(err2io) }
     }
 
     pub fn path_filestat_get(
@@ -188,7 +188,9 @@ impl WasiFd {
         flags: wasi::Lookupflags,
         path: &str,
     ) -> io::Result<wasi::Filestat> {
-        unsafe { wasi::path_filestat_get(self.fd as wasi::Fd, flags, path).map_err(err2io) }
+        unsafe {
+            wasi::path_filestat_get(self.as_raw_fd() as wasi::Fd, flags, path).map_err(err2io)
+        }
     }
 
     pub fn path_filestat_set_times(
@@ -200,21 +202,30 @@ impl WasiFd {
         fstflags: wasi::Fstflags,
     ) -> io::Result<()> {
         unsafe {
-            wasi::path_filestat_set_times(self.fd as wasi::Fd, flags, path, atim, mtim, fstflags)
-                .map_err(err2io)
+            wasi::path_filestat_set_times(
+                self.as_raw_fd() as wasi::Fd,
+                flags,
+                path,
+                atim,
+                mtim,
+                fstflags,
+            )
+            .map_err(err2io)
         }
     }
 
     pub fn symlink(&self, old_path: &str, new_path: &str) -> io::Result<()> {
-        unsafe { wasi::path_symlink(old_path, self.fd as wasi::Fd, new_path).map_err(err2io) }
+        unsafe {
+            wasi::path_symlink(old_path, self.as_raw_fd() as wasi::Fd, new_path).map_err(err2io)
+        }
     }
 
     pub fn unlink_file(&self, path: &str) -> io::Result<()> {
-        unsafe { wasi::path_unlink_file(self.fd as wasi::Fd, path).map_err(err2io) }
+        unsafe { wasi::path_unlink_file(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) }
     }
 
     pub fn remove_directory(&self, path: &str) -> io::Result<()> {
-        unsafe { wasi::path_remove_directory(self.fd as wasi::Fd, path).map_err(err2io) }
+        unsafe { wasi::path_remove_directory(self.as_raw_fd() as wasi::Fd, path).map_err(err2io) }
     }
 
     pub fn sock_recv(
@@ -222,11 +233,15 @@ impl WasiFd {
         ri_data: &mut [IoSliceMut<'_>],
         ri_flags: wasi::Riflags,
     ) -> io::Result<(usize, wasi::Roflags)> {
-        unsafe { wasi::sock_recv(self.fd as wasi::Fd, iovec(ri_data), ri_flags).map_err(err2io) }
+        unsafe {
+            wasi::sock_recv(self.as_raw_fd() as wasi::Fd, iovec(ri_data), ri_flags).map_err(err2io)
+        }
     }
 
     pub fn sock_send(&self, si_data: &[IoSlice<'_>], si_flags: wasi::Siflags) -> io::Result<usize> {
-        unsafe { wasi::sock_send(self.fd as wasi::Fd, ciovec(si_data), si_flags).map_err(err2io) }
+        unsafe {
+            wasi::sock_send(self.as_raw_fd() as wasi::Fd, ciovec(si_data), si_flags).map_err(err2io)
+        }
     }
 
     pub fn sock_shutdown(&self, how: Shutdown) -> io::Result<()> {
@@ -235,14 +250,54 @@ impl WasiFd {
             Shutdown::Write => wasi::SDFLAGS_WR,
             Shutdown::Both => wasi::SDFLAGS_WR | wasi::SDFLAGS_RD,
         };
-        unsafe { wasi::sock_shutdown(self.fd as wasi::Fd, how).map_err(err2io) }
+        unsafe { wasi::sock_shutdown(self.as_raw_fd() as wasi::Fd, how).map_err(err2io) }
+    }
+}
+
+impl AsInner<OwnedFd> for WasiFd {
+    fn as_inner(&self) -> &OwnedFd {
+        &self.fd
+    }
+}
+
+impl AsInnerMut<OwnedFd> for WasiFd {
+    fn as_inner_mut(&mut self) -> &mut OwnedFd {
+        &mut self.fd
+    }
+}
+
+impl IntoInner<OwnedFd> for WasiFd {
+    fn into_inner(self) -> OwnedFd {
+        self.fd
+    }
+}
+
+impl FromInner<OwnedFd> for WasiFd {
+    fn from_inner(owned_fd: OwnedFd) -> Self {
+        Self { fd: owned_fd }
+    }
+}
+
+impl AsFd for WasiFd {
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.fd.as_fd()
+    }
+}
+
+impl AsRawFd for WasiFd {
+    fn as_raw_fd(&self) -> RawFd {
+        self.fd.as_raw_fd()
+    }
+}
+
+impl IntoRawFd for WasiFd {
+    fn into_raw_fd(self) -> RawFd {
+        self.fd.into_raw_fd()
     }
 }
 
-impl Drop for WasiFd {
-    fn drop(&mut self) {
-        // FIXME: can we handle the return code here even though we can't on
-        // unix?
-        let _ = unsafe { wasi::fd_close(self.fd as wasi::Fd) };
+impl FromRawFd for WasiFd {
+    unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
+        unsafe { Self { fd: FromRawFd::from_raw_fd(raw_fd) } }
     }
 }
diff --git a/library/std/src/sys/wasi/fs.rs b/library/std/src/sys/wasi/fs.rs
index 55c9c652a8b..984dda8dc0b 100644
--- a/library/std/src/sys/wasi/fs.rs
+++ b/library/std/src/sys/wasi/fs.rs
@@ -8,12 +8,13 @@ use crate::iter;
 use crate::mem::{self, ManuallyDrop};
 use crate::os::raw::c_int;
 use crate::os::wasi::ffi::{OsStrExt, OsStringExt};
+use crate::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd};
 use crate::path::{Path, PathBuf};
 use crate::ptr;
 use crate::sync::Arc;
 use crate::sys::time::SystemTime;
 use crate::sys::unsupported;
-use crate::sys_common::FromInner;
+use crate::sys_common::{AsInner, FromInner, IntoInner};
 
 pub use crate::sys_common::fs::{remove_dir_all, try_exists};
 
@@ -442,22 +443,50 @@ impl File {
         unsupported()
     }
 
-    pub fn fd(&self) -> &WasiFd {
+    pub fn read_link(&self, file: &Path) -> io::Result<PathBuf> {
+        read_link(&self.fd, file)
+    }
+}
+
+impl AsInner<WasiFd> for File {
+    fn as_inner(&self) -> &WasiFd {
         &self.fd
     }
+}
 
-    pub fn into_fd(self) -> WasiFd {
+impl IntoInner<WasiFd> for File {
+    fn into_inner(self) -> WasiFd {
         self.fd
     }
+}
 
-    pub fn read_link(&self, file: &Path) -> io::Result<PathBuf> {
-        read_link(&self.fd, file)
+impl FromInner<WasiFd> for File {
+    fn from_inner(fd: WasiFd) -> File {
+        File { fd }
+    }
+}
+
+impl AsFd for File {
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.fd.as_fd()
+    }
+}
+
+impl AsRawFd for File {
+    fn as_raw_fd(&self) -> RawFd {
+        self.fd.as_raw_fd()
+    }
+}
+
+impl IntoRawFd for File {
+    fn into_raw_fd(self) -> RawFd {
+        self.fd.into_raw_fd()
     }
 }
 
-impl FromInner<c_int> for File {
-    fn from_inner(fd: c_int) -> File {
-        unsafe { File { fd: WasiFd::from_raw(fd) } }
+impl FromRawFd for File {
+    unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
+        unsafe { Self { fd: FromRawFd::from_raw_fd(raw_fd) } }
     }
 }
 
@@ -474,7 +503,7 @@ impl DirBuilder {
 
 impl fmt::Debug for File {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_struct("File").field("fd", &self.fd.as_raw()).finish()
+        f.debug_struct("File").field("fd", &self.as_raw_fd()).finish()
     }
 }
 
@@ -654,7 +683,7 @@ fn open_parent(p: &Path) -> io::Result<(ManuallyDrop<WasiFd>, PathBuf)> {
             let relative = CStr::from_ptr(relative_path).to_bytes().to_vec();
 
             return Ok((
-                ManuallyDrop::new(WasiFd::from_raw(fd as c_int)),
+                ManuallyDrop::new(WasiFd::from_raw_fd(fd as c_int)),
                 PathBuf::from(OsString::from_vec(relative)),
             ));
         }
diff --git a/library/std/src/sys/wasi/net.rs b/library/std/src/sys/wasi/net.rs
index 50b7352933e..c7c4a9f6efd 100644
--- a/library/std/src/sys/wasi/net.rs
+++ b/library/std/src/sys/wasi/net.rs
@@ -5,13 +5,57 @@ use crate::convert::TryFrom;
 use crate::fmt;
 use crate::io::{self, IoSlice, IoSliceMut};
 use crate::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr};
-use crate::os::raw::c_int;
+use crate::os::wasi::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, RawFd};
 use crate::sys::unsupported;
-use crate::sys_common::FromInner;
+use crate::sys_common::{AsInner, FromInner, IntoInner};
 use crate::time::Duration;
 
+pub struct Socket(WasiFd);
+
 pub struct TcpStream {
-    fd: WasiFd,
+    inner: Socket,
+}
+
+impl AsInner<WasiFd> for Socket {
+    fn as_inner(&self) -> &WasiFd {
+        &self.0
+    }
+}
+
+impl IntoInner<WasiFd> for Socket {
+    fn into_inner(self) -> WasiFd {
+        self.0
+    }
+}
+
+impl FromInner<WasiFd> for Socket {
+    fn from_inner(inner: WasiFd) -> Socket {
+        Socket(inner)
+    }
+}
+
+impl AsFd for Socket {
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.0.as_fd()
+    }
+}
+
+impl AsRawFd for Socket {
+    fn as_raw_fd(&self) -> RawFd {
+        self.0.as_raw_fd()
+    }
+}
+
+impl IntoRawFd for Socket {
+    fn into_raw_fd(self) -> RawFd {
+        self.0.into_raw_fd()
+    }
+}
+
+impl FromRawFd for Socket {
+    unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
+        unsafe { Self(FromRawFd::from_raw_fd(raw_fd)) }
+    }
 }
 
 impl TcpStream {
@@ -107,29 +151,29 @@ impl TcpStream {
         unsupported()
     }
 
-    pub fn fd(&self) -> &WasiFd {
-        &self.fd
+    pub fn socket(&self) -> &Socket {
+        &self.inner
     }
 
-    pub fn into_fd(self) -> WasiFd {
-        self.fd
+    pub fn into_socket(self) -> Socket {
+        self.inner
     }
 }
 
-impl FromInner<c_int> for TcpStream {
-    fn from_inner(fd: c_int) -> TcpStream {
-        unsafe { TcpStream { fd: WasiFd::from_raw(fd) } }
+impl FromInner<Socket> for TcpStream {
+    fn from_inner(socket: Socket) -> TcpStream {
+        TcpStream { inner: socket }
     }
 }
 
 impl fmt::Debug for TcpStream {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_struct("TcpStream").field("fd", &self.fd.as_raw()).finish()
+        f.debug_struct("TcpStream").field("fd", &self.inner.as_raw_fd()).finish()
     }
 }
 
 pub struct TcpListener {
-    fd: WasiFd,
+    inner: Socket,
 }
 
 impl TcpListener {
@@ -173,29 +217,41 @@ impl TcpListener {
         unsupported()
     }
 
-    pub fn fd(&self) -> &WasiFd {
-        &self.fd
+    pub fn socket(&self) -> &Socket {
+        &self.inner
     }
 
-    pub fn into_fd(self) -> WasiFd {
-        self.fd
+    pub fn into_socket(self) -> Socket {
+        self.inner
     }
 }
 
-impl FromInner<c_int> for TcpListener {
-    fn from_inner(fd: c_int) -> TcpListener {
-        unsafe { TcpListener { fd: WasiFd::from_raw(fd) } }
+impl AsInner<Socket> for TcpListener {
+    fn as_inner(&self) -> &Socket {
+        &self.inner
+    }
+}
+
+impl IntoInner<Socket> for TcpListener {
+    fn into_inner(self) -> Socket {
+        self.inner
+    }
+}
+
+impl FromInner<Socket> for TcpListener {
+    fn from_inner(inner: Socket) -> TcpListener {
+        TcpListener { inner }
     }
 }
 
 impl fmt::Debug for TcpListener {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_struct("TcpListener").field("fd", &self.fd.as_raw()).finish()
+        f.debug_struct("TcpListener").field("fd", &self.inner.as_raw_fd()).finish()
     }
 }
 
 pub struct UdpSocket {
-    fd: WasiFd,
+    inner: Socket,
 }
 
 impl UdpSocket {
@@ -323,24 +379,36 @@ impl UdpSocket {
         unsupported()
     }
 
-    pub fn fd(&self) -> &WasiFd {
-        &self.fd
+    pub fn socket(&self) -> &Socket {
+        &self.inner
+    }
+
+    pub fn into_socket(self) -> Socket {
+        self.inner
     }
+}
+
+impl AsInner<Socket> for UdpSocket {
+    fn as_inner(&self) -> &Socket {
+        &self.inner
+    }
+}
 
-    pub fn into_fd(self) -> WasiFd {
-        self.fd
+impl IntoInner<Socket> for UdpSocket {
+    fn into_inner(self) -> Socket {
+        self.inner
     }
 }
 
-impl FromInner<c_int> for UdpSocket {
-    fn from_inner(fd: c_int) -> UdpSocket {
-        unsafe { UdpSocket { fd: WasiFd::from_raw(fd) } }
+impl FromInner<Socket> for UdpSocket {
+    fn from_inner(inner: Socket) -> UdpSocket {
+        UdpSocket { inner }
     }
 }
 
 impl fmt::Debug for UdpSocket {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_struct("UdpSocket").field("fd", &self.fd.as_raw()).finish()
+        f.debug_struct("UdpSocket").field("fd", &self.inner.as_raw_fd()).finish()
     }
 }
 
diff --git a/library/std/src/sys/wasi/stdio.rs b/library/std/src/sys/wasi/stdio.rs
index 8782f333a1f..2c8f394cd47 100644
--- a/library/std/src/sys/wasi/stdio.rs
+++ b/library/std/src/sys/wasi/stdio.rs
@@ -4,6 +4,7 @@ use super::fd::WasiFd;
 use crate::io::{self, IoSlice, IoSliceMut};
 use crate::mem::ManuallyDrop;
 use crate::os::raw;
+use crate::os::wasi::io::{AsRawFd, FromRawFd};
 
 pub struct Stdin;
 pub struct Stdout;
@@ -13,9 +14,11 @@ impl Stdin {
     pub const fn new() -> Stdin {
         Stdin
     }
+}
 
+impl AsRawFd for Stdin {
     #[inline]
-    pub fn as_raw_fd(&self) -> raw::c_int {
+    fn as_raw_fd(&self) -> raw::c_int {
         0
     }
 }
@@ -26,7 +29,7 @@ impl io::Read for Stdin {
     }
 
     fn read_vectored(&mut self, data: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
-        ManuallyDrop::new(unsafe { WasiFd::from_raw(self.as_raw_fd()) }).read(data)
+        ManuallyDrop::new(unsafe { WasiFd::from_raw_fd(self.as_raw_fd()) }).read(data)
     }
 
     #[inline]
@@ -39,9 +42,11 @@ impl Stdout {
     pub const fn new() -> Stdout {
         Stdout
     }
+}
 
+impl AsRawFd for Stdout {
     #[inline]
-    pub fn as_raw_fd(&self) -> raw::c_int {
+    fn as_raw_fd(&self) -> raw::c_int {
         1
     }
 }
@@ -52,7 +57,7 @@ impl io::Write for Stdout {
     }
 
     fn write_vectored(&mut self, data: &[IoSlice<'_>]) -> io::Result<usize> {
-        ManuallyDrop::new(unsafe { WasiFd::from_raw(self.as_raw_fd()) }).write(data)
+        ManuallyDrop::new(unsafe { WasiFd::from_raw_fd(self.as_raw_fd()) }).write(data)
     }
 
     #[inline]
@@ -68,9 +73,11 @@ impl Stderr {
     pub const fn new() -> Stderr {
         Stderr
     }
+}
 
+impl AsRawFd for Stderr {
     #[inline]
-    pub fn as_raw_fd(&self) -> raw::c_int {
+    fn as_raw_fd(&self) -> raw::c_int {
         2
     }
 }
@@ -81,7 +88,7 @@ impl io::Write for Stderr {
     }
 
     fn write_vectored(&mut self, data: &[IoSlice<'_>]) -> io::Result<usize> {
-        ManuallyDrop::new(unsafe { WasiFd::from_raw(self.as_raw_fd()) }).write(data)
+        ManuallyDrop::new(unsafe { WasiFd::from_raw_fd(self.as_raw_fd()) }).write(data)
     }
 
     #[inline]
diff --git a/library/std/src/sys/windows/fs.rs b/library/std/src/sys/windows/fs.rs
index c677adae688..0c1a50e231c 100644
--- a/library/std/src/sys/windows/fs.rs
+++ b/library/std/src/sys/windows/fs.rs
@@ -4,6 +4,7 @@ use crate::ffi::OsString;
 use crate::fmt;
 use crate::io::{self, Error, IoSlice, IoSliceMut, SeekFrom};
 use crate::mem;
+use crate::os::windows::io::{AsHandle, BorrowedHandle};
 use crate::path::{Path, PathBuf};
 use crate::ptr;
 use crate::slice;
@@ -11,7 +12,7 @@ use crate::sync::Arc;
 use crate::sys::handle::Handle;
 use crate::sys::time::SystemTime;
 use crate::sys::{c, cvt};
-use crate::sys_common::FromInner;
+use crate::sys_common::{AsInner, FromInner, IntoInner};
 
 use super::to_u16s;
 
@@ -295,12 +296,12 @@ impl File {
         if handle == c::INVALID_HANDLE_VALUE {
             Err(Error::last_os_error())
         } else {
-            Ok(File { handle: Handle::new(handle) })
+            unsafe { Ok(File { handle: Handle::from_raw_handle(handle) }) }
         }
     }
 
     pub fn fsync(&self) -> io::Result<()> {
-        cvt(unsafe { c::FlushFileBuffers(self.handle.raw()) })?;
+        cvt(unsafe { c::FlushFileBuffers(self.handle.as_raw_handle()) })?;
         Ok(())
     }
 
@@ -313,7 +314,7 @@ impl File {
         let size = mem::size_of_val(&info);
         cvt(unsafe {
             c::SetFileInformationByHandle(
-                self.handle.raw(),
+                self.handle.as_raw_handle(),
                 c::FileEndOfFileInfo,
                 &mut info as *mut _ as *mut _,
                 size as c::DWORD,
@@ -326,7 +327,7 @@ impl File {
     pub fn file_attr(&self) -> io::Result<FileAttr> {
         unsafe {
             let mut info: c::BY_HANDLE_FILE_INFORMATION = mem::zeroed();
-            cvt(c::GetFileInformationByHandle(self.handle.raw(), &mut info))?;
+            cvt(c::GetFileInformationByHandle(self.handle.as_raw_handle(), &mut info))?;
             let mut reparse_tag = 0;
             if info.dwFileAttributes & c::FILE_ATTRIBUTE_REPARSE_POINT != 0 {
                 let mut b = [0; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
@@ -449,7 +450,7 @@ impl File {
         };
         let pos = pos as c::LARGE_INTEGER;
         let mut newpos = 0;
-        cvt(unsafe { c::SetFilePointerEx(self.handle.raw(), pos, &mut newpos, whence) })?;
+        cvt(unsafe { c::SetFilePointerEx(self.handle.as_raw_handle(), pos, &mut newpos, whence) })?;
         Ok(newpos as u64)
     }
 
@@ -457,14 +458,6 @@ impl File {
         Ok(File { handle: self.handle.duplicate(0, false, c::DUPLICATE_SAME_ACCESS)? })
     }
 
-    pub fn handle(&self) -> &Handle {
-        &self.handle
-    }
-
-    pub fn into_handle(self) -> Handle {
-        self.handle
-    }
-
     fn reparse_point<'a>(
         &self,
         space: &'a mut [u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE],
@@ -473,7 +466,7 @@ impl File {
             let mut bytes = 0;
             cvt({
                 c::DeviceIoControl(
-                    self.handle.raw(),
+                    self.handle.as_raw_handle(),
                     c::FSCTL_GET_REPARSE_POINT,
                     ptr::null_mut(),
                     0,
@@ -541,7 +534,7 @@ impl File {
         let size = mem::size_of_val(&info);
         cvt(unsafe {
             c::SetFileInformationByHandle(
-                self.handle.raw(),
+                self.handle.as_raw_handle(),
                 c::FileBasicInfo,
                 &mut info as *mut _ as *mut _,
                 size as c::DWORD,
@@ -551,9 +544,45 @@ impl File {
     }
 }
 
-impl FromInner<c::HANDLE> for File {
-    fn from_inner(handle: c::HANDLE) -> File {
-        File { handle: Handle::new(handle) }
+impl AsInner<Handle> for File {
+    fn as_inner(&self) -> &Handle {
+        &self.handle
+    }
+}
+
+impl IntoInner<Handle> for File {
+    fn into_inner(self) -> Handle {
+        self.handle
+    }
+}
+
+impl FromInner<Handle> for File {
+    fn from_inner(handle: Handle) -> File {
+        File { handle: handle }
+    }
+}
+
+impl AsHandle for File {
+    fn as_handle(&self) -> BorrowedHandle<'_> {
+        self.as_inner().as_handle()
+    }
+}
+
+impl AsRawHandle for File {
+    fn as_raw_handle(&self) -> RawHandle {
+        self.as_inner().as_raw_handle()
+    }
+}
+
+impl IntoRawHandle for File {
+    fn into_raw_handle(self) -> RawHandle {
+        self.into_inner().into_raw_handle()
+    }
+}
+
+impl FromRawHandle for File {
+    unsafe fn from_raw_handle(raw_handle: RawHandle) -> Self {
+        Self { handle: FromInner::from_inner(FromRawHandle::from_raw_handle(raw_handle)) }
     }
 }
 
@@ -561,7 +590,7 @@ impl fmt::Debug for File {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         // FIXME(#24570): add more info here (e.g., mode)
         let mut b = f.debug_struct("File");
-        b.field("handle", &self.handle.raw());
+        b.field("handle", &self.handle.as_raw_handle());
         if let Ok(path) = get_path(&self) {
             b.field("path", &path);
         }
@@ -838,7 +867,7 @@ pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> {
 fn get_path(f: &File) -> io::Result<PathBuf> {
     super::fill_utf16_buf(
         |buf, sz| unsafe {
-            c::GetFinalPathNameByHandleW(f.handle.raw(), buf, sz, c::VOLUME_NAME_DOS)
+            c::GetFinalPathNameByHandleW(f.handle.as_raw_handle(), buf, sz, c::VOLUME_NAME_DOS)
         },
         |buf| PathBuf::from(OsString::from_wide(buf)),
     )
@@ -909,7 +938,7 @@ fn symlink_junction_inner(original: &Path, junction: &Path) -> io::Result<()> {
     opts.write(true);
     opts.custom_flags(c::FILE_FLAG_OPEN_REPARSE_POINT | c::FILE_FLAG_BACKUP_SEMANTICS);
     let f = File::open(junction, &opts)?;
-    let h = f.handle().raw();
+    let h = f.as_inner().as_raw_handle();
 
     unsafe {
         let mut data = [0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
diff --git a/library/std/src/sys/windows/handle.rs b/library/std/src/sys/windows/handle.rs
index 0d4baa3b340..21d86b00226 100644
--- a/library/std/src/sys/windows/handle.rs
+++ b/library/std/src/sys/windows/handle.rs
@@ -3,76 +3,87 @@
 use crate::cmp;
 use crate::io::{self, ErrorKind, IoSlice, IoSliceMut, Read};
 use crate::mem;
-use crate::ops::Deref;
+use crate::os::windows::io::{
+    AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, IntoRawHandle, OwnedHandle, RawHandle,
+};
 use crate::ptr;
 use crate::sys::c;
 use crate::sys::cvt;
+use crate::sys_common::{AsInner, FromInner, IntoInner};
 
 /// An owned container for `HANDLE` object, closing them on Drop.
 ///
 /// All methods are inherited through a `Deref` impl to `RawHandle`
-pub struct Handle(RawHandle);
-
-/// A wrapper type for `HANDLE` objects to give them proper Send/Sync inference
-/// as well as Rust-y methods.
-///
-/// This does **not** drop the handle when it goes out of scope, use `Handle`
-/// instead for that.
-#[derive(Copy, Clone)]
-pub struct RawHandle(c::HANDLE);
-
-unsafe impl Send for RawHandle {}
-unsafe impl Sync for RawHandle {}
+pub struct Handle(OwnedHandle);
 
 impl Handle {
-    pub fn new(handle: c::HANDLE) -> Handle {
-        Handle(RawHandle::new(handle))
-    }
-
     pub fn new_event(manual: bool, init: bool) -> io::Result<Handle> {
         unsafe {
             let event =
                 c::CreateEventW(ptr::null_mut(), manual as c::BOOL, init as c::BOOL, ptr::null());
-            if event.is_null() { Err(io::Error::last_os_error()) } else { Ok(Handle::new(event)) }
+            if event.is_null() {
+                Err(io::Error::last_os_error())
+            } else {
+                Ok(Handle::from_raw_handle(event))
+            }
         }
     }
+}
 
-    pub fn into_raw(self) -> c::HANDLE {
-        let ret = self.raw();
-        mem::forget(self);
-        ret
+impl AsInner<OwnedHandle> for Handle {
+    fn as_inner(&self) -> &OwnedHandle {
+        &self.0
     }
 }
 
-impl Deref for Handle {
-    type Target = RawHandle;
-    fn deref(&self) -> &RawHandle {
-        &self.0
+impl IntoInner<OwnedHandle> for Handle {
+    fn into_inner(self) -> OwnedHandle {
+        self.0
     }
 }
 
-impl Drop for Handle {
-    fn drop(&mut self) {
-        unsafe {
-            let _ = c::CloseHandle(self.raw());
-        }
+impl FromInner<OwnedHandle> for Handle {
+    fn from_inner(file_desc: OwnedHandle) -> Self {
+        Self(file_desc)
     }
 }
 
-impl RawHandle {
-    pub fn new(handle: c::HANDLE) -> RawHandle {
-        RawHandle(handle)
+impl AsHandle for Handle {
+    fn as_handle(&self) -> BorrowedHandle<'_> {
+        self.0.as_handle()
     }
+}
 
-    pub fn raw(&self) -> c::HANDLE {
-        self.0
+impl AsRawHandle for Handle {
+    fn as_raw_handle(&self) -> RawHandle {
+        self.0.as_raw_handle()
     }
+}
 
+impl IntoRawHandle for Handle {
+    fn into_raw_handle(self) -> RawHandle {
+        self.0.into_raw_handle()
+    }
+}
+
+impl FromRawHandle for Handle {
+    unsafe fn from_raw_handle(raw_handle: RawHandle) -> Self {
+        Self(FromRawHandle::from_raw_handle(raw_handle))
+    }
+}
+
+impl Handle {
     pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
         let mut read = 0;
         let len = cmp::min(buf.len(), <c::DWORD>::MAX as usize) as c::DWORD;
         let res = cvt(unsafe {
-            c::ReadFile(self.0, buf.as_mut_ptr() as c::LPVOID, len, &mut read, ptr::null_mut())
+            c::ReadFile(
+                self.as_raw_handle(),
+                buf.as_mut_ptr() as c::LPVOID,
+                len,
+                &mut read,
+                ptr::null_mut(),
+            )
         });
 
         match res {
@@ -104,7 +115,13 @@ impl RawHandle {
             let mut overlapped: c::OVERLAPPED = mem::zeroed();
             overlapped.Offset = offset as u32;
             overlapped.OffsetHigh = (offset >> 32) as u32;
-            cvt(c::ReadFile(self.0, buf.as_mut_ptr() as c::LPVOID, len, &mut read, &mut overlapped))
+            cvt(c::ReadFile(
+                self.as_raw_handle(),
+                buf.as_mut_ptr() as c::LPVOID,
+                len,
+                &mut read,
+                &mut overlapped,
+            ))
         };
         match res {
             Ok(_) => Ok(read as usize),
@@ -120,7 +137,13 @@ impl RawHandle {
     ) -> io::Result<Option<usize>> {
         let len = cmp::min(buf.len(), <c::DWORD>::MAX as usize) as c::DWORD;
         let mut amt = 0;
-        let res = cvt(c::ReadFile(self.0, buf.as_ptr() as c::LPVOID, len, &mut amt, overlapped));
+        let res = cvt(c::ReadFile(
+            self.as_raw_handle(),
+            buf.as_ptr() as c::LPVOID,
+            len,
+            &mut amt,
+            overlapped,
+        ));
         match res {
             Ok(_) => Ok(Some(amt as usize)),
             Err(e) => {
@@ -143,7 +166,8 @@ impl RawHandle {
         unsafe {
             let mut bytes = 0;
             let wait = if wait { c::TRUE } else { c::FALSE };
-            let res = cvt(c::GetOverlappedResult(self.raw(), overlapped, &mut bytes, wait));
+            let res =
+                cvt(c::GetOverlappedResult(self.as_raw_handle(), overlapped, &mut bytes, wait));
             match res {
                 Ok(_) => Ok(bytes as usize),
                 Err(e) => {
@@ -160,14 +184,20 @@ impl RawHandle {
     }
 
     pub fn cancel_io(&self) -> io::Result<()> {
-        unsafe { cvt(c::CancelIo(self.raw())).map(drop) }
+        unsafe { cvt(c::CancelIo(self.as_raw_handle())).map(drop) }
     }
 
     pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
         let mut amt = 0;
         let len = cmp::min(buf.len(), <c::DWORD>::MAX as usize) as c::DWORD;
         cvt(unsafe {
-            c::WriteFile(self.0, buf.as_ptr() as c::LPVOID, len, &mut amt, ptr::null_mut())
+            c::WriteFile(
+                self.as_raw_handle(),
+                buf.as_ptr() as c::LPVOID,
+                len,
+                &mut amt,
+                ptr::null_mut(),
+            )
         })?;
         Ok(amt as usize)
     }
@@ -189,7 +219,7 @@ impl RawHandle {
             overlapped.Offset = offset as u32;
             overlapped.OffsetHigh = (offset >> 32) as u32;
             cvt(c::WriteFile(
-                self.0,
+                self.as_raw_handle(),
                 buf.as_ptr() as c::LPVOID,
                 len,
                 &mut written,
@@ -210,7 +240,7 @@ impl RawHandle {
             let cur_proc = c::GetCurrentProcess();
             c::DuplicateHandle(
                 cur_proc,
-                self.0,
+                self.as_raw_handle(),
                 cur_proc,
                 &mut ret,
                 access,
@@ -218,11 +248,11 @@ impl RawHandle {
                 options,
             )
         })?;
-        Ok(Handle::new(ret))
+        unsafe { Ok(Handle::from_raw_handle(ret)) }
     }
 }
 
-impl<'a> Read for &'a RawHandle {
+impl<'a> Read for &'a Handle {
     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
         (**self).read(buf)
     }
diff --git a/library/std/src/sys/windows/net.rs b/library/std/src/sys/windows/net.rs
index 9cea5c5e63a..55aacb38c6f 100644
--- a/library/std/src/sys/windows/net.rs
+++ b/library/std/src/sys/windows/net.rs
@@ -4,6 +4,9 @@ use crate::cmp;
 use crate::io::{self, IoSlice, IoSliceMut, Read};
 use crate::mem;
 use crate::net::{Shutdown, SocketAddr};
+use crate::os::windows::io::{
+    AsRawSocket, AsSocket, BorrowedSocket, FromRawSocket, IntoRawSocket, OwnedSocket, RawSocket,
+};
 use crate::ptr;
 use crate::sync::Once;
 use crate::sys;
@@ -24,7 +27,7 @@ pub mod netc {
     pub use crate::sys::c::*;
 }
 
-pub struct Socket(c::SOCKET);
+pub struct Socket(OwnedSocket);
 
 static INIT: Once = Once::new();
 
@@ -109,7 +112,7 @@ impl Socket {
         };
 
         if socket != c::INVALID_SOCKET {
-            Ok(Self(socket))
+            unsafe { Ok(Self::from_raw_socket(socket)) }
         } else {
             let error = unsafe { c::WSAGetLastError() };
 
@@ -124,9 +127,11 @@ impl Socket {
                 return Err(last_error());
             }
 
-            let socket = Self(socket);
-            socket.set_no_inherit()?;
-            Ok(socket)
+            unsafe {
+                let socket = Self::from_raw_socket(socket);
+                socket.set_no_inherit()?;
+                Ok(socket)
+            }
         }
     }
 
@@ -134,7 +139,7 @@ impl Socket {
         self.set_nonblocking(true)?;
         let result = {
             let (addrp, len) = addr.into_inner();
-            let result = unsafe { c::connect(self.0, addrp, len) };
+            let result = unsafe { c::connect(self.as_raw_socket(), addrp, len) };
             cvt(result).map(drop)
         };
         self.set_nonblocking(false)?;
@@ -160,7 +165,7 @@ impl Socket {
                 let fds = {
                     let mut fds = unsafe { mem::zeroed::<c::fd_set>() };
                     fds.fd_count = 1;
-                    fds.fd_array[0] = self.0;
+                    fds.fd_array[0] = self.as_raw_socket();
                     fds
                 };
 
@@ -194,17 +199,19 @@ impl Socket {
     }
 
     pub fn accept(&self, storage: *mut c::SOCKADDR, len: *mut c_int) -> io::Result<Socket> {
-        let socket = unsafe { c::accept(self.0, storage, len) };
+        let socket = unsafe { c::accept(self.as_raw_socket(), storage, len) };
 
         match socket {
             c::INVALID_SOCKET => Err(last_error()),
-            _ => Ok(Self(socket)),
+            _ => unsafe { Ok(Self::from_raw_socket(socket)) },
         }
     }
 
     pub fn duplicate(&self) -> io::Result<Socket> {
         let mut info = unsafe { mem::zeroed::<c::WSAPROTOCOL_INFO>() };
-        let result = unsafe { c::WSADuplicateSocketW(self.0, c::GetCurrentProcessId(), &mut info) };
+        let result = unsafe {
+            c::WSADuplicateSocketW(self.as_raw_socket(), c::GetCurrentProcessId(), &mut info)
+        };
         cvt(result)?;
         let socket = unsafe {
             c::WSASocketW(
@@ -218,7 +225,7 @@ impl Socket {
         };
 
         if socket != c::INVALID_SOCKET {
-            Ok(Self(socket))
+            unsafe { Ok(Self::from_inner(OwnedSocket::from_raw_socket(socket))) }
         } else {
             let error = unsafe { c::WSAGetLastError() };
 
@@ -241,9 +248,11 @@ impl Socket {
                 return Err(last_error());
             }
 
-            let socket = Self(socket);
-            socket.set_no_inherit()?;
-            Ok(socket)
+            unsafe {
+                let socket = Self::from_inner(OwnedSocket::from_raw_socket(socket));
+                socket.set_no_inherit()?;
+                Ok(socket)
+            }
         }
     }
 
@@ -251,7 +260,8 @@ impl Socket {
         // On unix when a socket is shut down all further reads return 0, so we
         // do the same on windows to map a shut down socket to returning EOF.
         let length = cmp::min(buf.len(), i32::MAX as usize) as i32;
-        let result = unsafe { c::recv(self.0, buf.as_mut_ptr() as *mut _, length, flags) };
+        let result =
+            unsafe { c::recv(self.as_raw_socket(), buf.as_mut_ptr() as *mut _, length, flags) };
 
         match result {
             c::SOCKET_ERROR => {
@@ -279,7 +289,7 @@ impl Socket {
         let mut flags = 0;
         let result = unsafe {
             c::WSARecv(
-                self.0,
+                self.as_raw_socket(),
                 bufs.as_mut_ptr() as *mut c::WSABUF,
                 length,
                 &mut nread,
@@ -325,7 +335,7 @@ impl Socket {
         // do the same on windows to map a shut down socket to returning EOF.
         let result = unsafe {
             c::recvfrom(
-                self.0,
+                self.as_raw_socket(),
                 buf.as_mut_ptr() as *mut _,
                 length,
                 flags,
@@ -361,7 +371,7 @@ impl Socket {
         let mut nwritten = 0;
         let result = unsafe {
             c::WSASend(
-                self.0,
+                self.as_raw_socket(),
                 bufs.as_ptr() as *const c::WSABUF as *mut _,
                 length,
                 &mut nwritten,
@@ -408,8 +418,10 @@ impl Socket {
 
     #[cfg(not(target_vendor = "uwp"))]
     fn set_no_inherit(&self) -> io::Result<()> {
-        sys::cvt(unsafe { c::SetHandleInformation(self.0 as c::HANDLE, c::HANDLE_FLAG_INHERIT, 0) })
-            .map(drop)
+        sys::cvt(unsafe {
+            c::SetHandleInformation(self.as_raw_socket() as c::HANDLE, c::HANDLE_FLAG_INHERIT, 0)
+        })
+        .map(drop)
     }
 
     #[cfg(target_vendor = "uwp")]
@@ -423,13 +435,14 @@ impl Socket {
             Shutdown::Read => c::SD_RECEIVE,
             Shutdown::Both => c::SD_BOTH,
         };
-        let result = unsafe { c::shutdown(self.0, how) };
+        let result = unsafe { c::shutdown(self.as_raw_socket(), how) };
         cvt(result).map(drop)
     }
 
     pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
         let mut nonblocking = nonblocking as c_ulong;
-        let result = unsafe { c::ioctlsocket(self.0, c::FIONBIO as c_int, &mut nonblocking) };
+        let result =
+            unsafe { c::ioctlsocket(self.as_raw_socket(), c::FIONBIO as c_int, &mut nonblocking) };
         cvt(result).map(drop)
     }
 
@@ -446,6 +459,11 @@ impl Socket {
         let raw: c_int = net::getsockopt(self, c::SOL_SOCKET, c::SO_ERROR)?;
         if raw == 0 { Ok(None) } else { Ok(Some(io::Error::from_raw_os_error(raw as i32))) }
     }
+
+    // This is used by sys_common code to abstract over Windows and Unix.
+    pub fn as_raw(&self) -> RawSocket {
+        self.as_inner().as_raw_socket()
+    }
 }
 
 #[unstable(reason = "not public", issue = "none", feature = "fd_read")]
@@ -455,28 +473,44 @@ impl<'a> Read for &'a Socket {
     }
 }
 
-impl Drop for Socket {
-    fn drop(&mut self) {
-        let _ = unsafe { c::closesocket(self.0) };
+impl AsInner<OwnedSocket> for Socket {
+    fn as_inner(&self) -> &OwnedSocket {
+        &self.0
     }
 }
 
-impl AsInner<c::SOCKET> for Socket {
-    fn as_inner(&self) -> &c::SOCKET {
-        &self.0
+impl FromInner<OwnedSocket> for Socket {
+    fn from_inner(sock: OwnedSocket) -> Socket {
+        Socket(sock)
     }
 }
 
-impl FromInner<c::SOCKET> for Socket {
-    fn from_inner(sock: c::SOCKET) -> Socket {
-        Socket(sock)
+impl IntoInner<OwnedSocket> for Socket {
+    fn into_inner(self) -> OwnedSocket {
+        self.0
+    }
+}
+
+impl AsSocket for Socket {
+    fn as_socket(&self) -> BorrowedSocket<'_> {
+        self.0.as_socket()
+    }
+}
+
+impl AsRawSocket for Socket {
+    fn as_raw_socket(&self) -> RawSocket {
+        self.0.as_raw_socket()
+    }
+}
+
+impl IntoRawSocket for Socket {
+    fn into_raw_socket(self) -> RawSocket {
+        self.0.into_raw_socket()
     }
 }
 
-impl IntoInner<c::SOCKET> for Socket {
-    fn into_inner(self) -> c::SOCKET {
-        let ret = self.0;
-        mem::forget(self);
-        ret
+impl FromRawSocket for Socket {
+    unsafe fn from_raw_socket(raw_socket: RawSocket) -> Self {
+        Self(FromRawSocket::from_raw_socket(raw_socket))
     }
 }
diff --git a/library/std/src/sys/windows/os.rs b/library/std/src/sys/windows/os.rs
index 8db97ba50a8..883690c4831 100644
--- a/library/std/src/sys/windows/os.rs
+++ b/library/std/src/sys/windows/os.rs
@@ -288,7 +288,7 @@ fn home_dir_crt() -> Option<PathBuf> {
         if c::OpenProcessToken(me, c::TOKEN_READ, &mut token) == 0 {
             return None;
         }
-        let _handle = Handle::new(token);
+        let _handle = Handle::from_raw_handle(token);
         super::fill_utf16_buf(
             |buf, mut sz| {
                 match c::GetUserProfileDirectoryW(token, buf, &mut sz) {
diff --git a/library/std/src/sys/windows/pipe.rs b/library/std/src/sys/windows/pipe.rs
index 104a8db4659..63d3d6c5ed4 100644
--- a/library/std/src/sys/windows/pipe.rs
+++ b/library/std/src/sys/windows/pipe.rs
@@ -12,6 +12,7 @@ use crate::sys::c;
 use crate::sys::fs::{File, OpenOptions};
 use crate::sys::handle::Handle;
 use crate::sys::hashmap_random_keys;
+use crate::sys_common::IntoInner;
 
 ////////////////////////////////////////////////////////////////////////////////
 // Anonymous pipes
@@ -21,6 +22,12 @@ pub struct AnonPipe {
     inner: Handle,
 }
 
+impl IntoInner<Handle> for AnonPipe {
+    fn into_inner(self) -> Handle {
+        self.inner
+    }
+}
+
 pub struct Pipes {
     pub ours: AnonPipe,
     pub theirs: AnonPipe,
@@ -123,7 +130,7 @@ pub fn anon_pipe(ours_readable: bool, their_handle_inheritable: bool) -> io::Res
                 }
                 return Err(err);
             }
-            ours = Handle::new(handle);
+            ours = Handle::from_raw_handle(handle);
             break;
         }
 
@@ -146,11 +153,11 @@ pub fn anon_pipe(ours_readable: bool, their_handle_inheritable: bool) -> io::Res
         };
         opts.security_attributes(&mut sa);
         let theirs = File::open(Path::new(&name), &opts)?;
-        let theirs = AnonPipe { inner: theirs.into_handle() };
+        let theirs = AnonPipe { inner: theirs.into_inner() };
 
         Ok(Pipes {
             ours: AnonPipe { inner: ours },
-            theirs: AnonPipe { inner: theirs.into_handle() },
+            theirs: AnonPipe { inner: theirs.into_inner() },
         })
     }
 }
@@ -207,7 +214,7 @@ pub fn read2(p1: AnonPipe, v1: &mut Vec<u8>, p2: AnonPipe, v2: &mut Vec<u8>) ->
 
     let mut p1 = AsyncPipe::new(p1, v1)?;
     let mut p2 = AsyncPipe::new(p2, v2)?;
-    let objs = [p1.event.raw(), p2.event.raw()];
+    let objs = [p1.event.as_raw_handle(), p2.event.as_raw_handle()];
 
     // In a loop we wait for either pipe's scheduled read operation to complete.
     // If the operation completes with 0 bytes, that means EOF was reached, in
@@ -262,7 +269,7 @@ impl<'a> AsyncPipe<'a> {
         // I/O operation is successfully scheduled (what we want).
         let event = Handle::new_event(true, true)?;
         let mut overlapped: Box<c::OVERLAPPED> = unsafe { Box::new(mem::zeroed()) };
-        overlapped.hEvent = event.raw();
+        overlapped.hEvent = event.as_raw_handle();
         Ok(AsyncPipe { pipe, overlapped, event, dst, state: State::NotReading })
     }
 
diff --git a/library/std/src/sys/windows/process.rs b/library/std/src/sys/windows/process.rs
index ae193b82e91..ccff90629a3 100644
--- a/library/std/src/sys/windows/process.rs
+++ b/library/std/src/sys/windows/process.rs
@@ -15,6 +15,7 @@ use crate::io::{self, Error, ErrorKind};
 use crate::mem;
 use crate::num::NonZeroI32;
 use crate::os::windows::ffi::OsStrExt;
+use crate::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle};
 use crate::path::Path;
 use crate::ptr;
 use crate::sys::c;
@@ -26,7 +27,7 @@ use crate::sys::pipe::{self, AnonPipe};
 use crate::sys::stdio;
 use crate::sys_common::mutex::StaticMutex;
 use crate::sys_common::process::{CommandEnv, CommandEnvs};
-use crate::sys_common::AsInner;
+use crate::sys_common::{AsInner, IntoInner};
 
 use libc::{c_void, EXIT_FAILURE, EXIT_SUCCESS};
 
@@ -316,9 +317,9 @@ impl Command {
         let stdin = stdin.to_handle(c::STD_INPUT_HANDLE, &mut pipes.stdin)?;
         let stdout = stdout.to_handle(c::STD_OUTPUT_HANDLE, &mut pipes.stdout)?;
         let stderr = stderr.to_handle(c::STD_ERROR_HANDLE, &mut pipes.stderr)?;
-        si.hStdInput = stdin.raw();
-        si.hStdOutput = stdout.raw();
-        si.hStdError = stderr.raw();
+        si.hStdInput = stdin.as_raw_handle();
+        si.hStdOutput = stdout.as_raw_handle();
+        si.hStdError = stderr.as_raw_handle();
 
         unsafe {
             cvt(c::CreateProcessW(
@@ -338,9 +339,11 @@ impl Command {
         // We close the thread handle because we don't care about keeping
         // the thread id valid, and we aren't keeping the thread handle
         // around to be able to close it later.
-        drop(Handle::new(pi.hThread));
+        unsafe {
+            drop(Handle::from_raw_handle(pi.hThread));
 
-        Ok((Process { handle: Handle::new(pi.hProcess) }, pipes))
+            Ok((Process { handle: Handle::from_raw_handle(pi.hProcess) }, pipes))
+        }
     }
 }
 
@@ -365,13 +368,13 @@ impl Stdio {
             // should still be unavailable so propagate the
             // INVALID_HANDLE_VALUE.
             Stdio::Inherit => match stdio::get_handle(stdio_id) {
-                Ok(io) => {
-                    let io = Handle::new(io);
+                Ok(io) => unsafe {
+                    let io = Handle::from_raw_handle(io);
                     let ret = io.duplicate(0, true, c::DUPLICATE_SAME_ACCESS);
-                    io.into_raw();
+                    io.into_raw_handle();
                     ret
-                }
-                Err(..) => Ok(Handle::new(c::INVALID_HANDLE_VALUE)),
+                },
+                Err(..) => unsafe { Ok(Handle::from_raw_handle(c::INVALID_HANDLE_VALUE)) },
             },
 
             Stdio::MakePipe => {
@@ -397,7 +400,7 @@ impl Stdio {
                 opts.read(stdio_id == c::STD_INPUT_HANDLE);
                 opts.write(stdio_id != c::STD_INPUT_HANDLE);
                 opts.security_attributes(&mut sa);
-                File::open(Path::new("NUL"), &opts).map(|file| file.into_handle())
+                File::open(Path::new("NUL"), &opts).map(|file| file.into_inner())
             }
         }
     }
@@ -411,7 +414,7 @@ impl From<AnonPipe> for Stdio {
 
 impl From<File> for Stdio {
     fn from(file: File) -> Stdio {
-        Stdio::Handle(file.into_handle())
+        Stdio::Handle(file.into_inner())
     }
 }
 
@@ -430,29 +433,29 @@ pub struct Process {
 
 impl Process {
     pub fn kill(&mut self) -> io::Result<()> {
-        cvt(unsafe { c::TerminateProcess(self.handle.raw(), 1) })?;
+        cvt(unsafe { c::TerminateProcess(self.handle.as_raw_handle(), 1) })?;
         Ok(())
     }
 
     pub fn id(&self) -> u32 {
-        unsafe { c::GetProcessId(self.handle.raw()) as u32 }
+        unsafe { c::GetProcessId(self.handle.as_raw_handle()) as u32 }
     }
 
     pub fn wait(&mut self) -> io::Result<ExitStatus> {
         unsafe {
-            let res = c::WaitForSingleObject(self.handle.raw(), c::INFINITE);
+            let res = c::WaitForSingleObject(self.handle.as_raw_handle(), c::INFINITE);
             if res != c::WAIT_OBJECT_0 {
                 return Err(Error::last_os_error());
             }
             let mut status = 0;
-            cvt(c::GetExitCodeProcess(self.handle.raw(), &mut status))?;
+            cvt(c::GetExitCodeProcess(self.handle.as_raw_handle(), &mut status))?;
             Ok(ExitStatus(status))
         }
     }
 
     pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> {
         unsafe {
-            match c::WaitForSingleObject(self.handle.raw(), 0) {
+            match c::WaitForSingleObject(self.handle.as_raw_handle(), 0) {
                 c::WAIT_OBJECT_0 => {}
                 c::WAIT_TIMEOUT => {
                     return Ok(None);
@@ -460,7 +463,7 @@ impl Process {
                 _ => return Err(io::Error::last_os_error()),
             }
             let mut status = 0;
-            cvt(c::GetExitCodeProcess(self.handle.raw(), &mut status))?;
+            cvt(c::GetExitCodeProcess(self.handle.as_raw_handle(), &mut status))?;
             Ok(Some(ExitStatus(status)))
         }
     }
diff --git a/library/std/src/sys/windows/stdio.rs b/library/std/src/sys/windows/stdio.rs
index 2973951fe90..1cf0e9f0cf1 100644
--- a/library/std/src/sys/windows/stdio.rs
+++ b/library/std/src/sys/windows/stdio.rs
@@ -3,6 +3,7 @@
 use crate::char::decode_utf16;
 use crate::cmp;
 use crate::io;
+use crate::os::windows::io::{FromRawHandle, IntoRawHandle};
 use crate::ptr;
 use crate::str;
 use crate::sys::c;
@@ -53,10 +54,12 @@ fn is_console(handle: c::HANDLE) -> bool {
 fn write(handle_id: c::DWORD, data: &[u8]) -> io::Result<usize> {
     let handle = get_handle(handle_id)?;
     if !is_console(handle) {
-        let handle = Handle::new(handle);
-        let ret = handle.write(data);
-        handle.into_raw(); // Don't close the handle
-        return ret;
+        unsafe {
+            let handle = Handle::from_raw_handle(handle);
+            let ret = handle.write(data);
+            handle.into_raw_handle(); // Don't close the handle
+            return ret;
+        }
     }
 
     // As the console is meant for presenting text, we assume bytes of `data` come from a string
@@ -140,10 +143,12 @@ impl io::Read for Stdin {
     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
         let handle = get_handle(c::STD_INPUT_HANDLE)?;
         if !is_console(handle) {
-            let handle = Handle::new(handle);
-            let ret = handle.read(buf);
-            handle.into_raw(); // Don't close the handle
-            return ret;
+            unsafe {
+                let handle = Handle::from_raw_handle(handle);
+                let ret = handle.read(buf);
+                handle.into_raw_handle(); // Don't close the handle
+                return ret;
+            }
         }
 
         if buf.len() == 0 {
diff --git a/library/std/src/sys/windows/thread.rs b/library/std/src/sys/windows/thread.rs
index ef7a9733fd8..a5293133b3a 100644
--- a/library/std/src/sys/windows/thread.rs
+++ b/library/std/src/sys/windows/thread.rs
@@ -1,6 +1,7 @@
 use crate::ffi::CStr;
 use crate::io;
 use crate::num::NonZeroUsize;
+use crate::os::windows::io::{AsRawHandle, FromRawHandle};
 use crate::ptr;
 use crate::sys::c;
 use crate::sys::handle::Handle;
@@ -45,7 +46,7 @@ impl Thread {
             drop(Box::from_raw(p));
             Err(io::Error::last_os_error())
         } else {
-            Ok(Thread { handle: Handle::new(ret) })
+            Ok(Thread { handle: Handle::from_raw_handle(ret) })
         };
 
         extern "system" fn thread_start(main: *mut c_void) -> c::DWORD {
@@ -71,7 +72,7 @@ impl Thread {
     }
 
     pub fn join(self) {
-        let rc = unsafe { c::WaitForSingleObject(self.handle.raw(), c::INFINITE) };
+        let rc = unsafe { c::WaitForSingleObject(self.handle.as_raw_handle(), c::INFINITE) };
         if rc == c::WAIT_FAILED {
             panic!("failed to join on thread: {}", io::Error::last_os_error());
         }
diff --git a/library/std/src/sys_common/net.rs b/library/std/src/sys_common/net.rs
index d5f29c4a439..0ffa5c01dd3 100644
--- a/library/std/src/sys_common/net.rs
+++ b/library/std/src/sys_common/net.rs
@@ -61,13 +61,7 @@ cfg_if::cfg_if! {
 pub fn setsockopt<T>(sock: &Socket, opt: c_int, val: c_int, payload: T) -> io::Result<()> {
     unsafe {
         let payload = &payload as *const T as *const c_void;
-        cvt(c::setsockopt(
-            *sock.as_inner(),
-            opt,
-            val,
-            payload,
-            mem::size_of::<T>() as c::socklen_t,
-        ))?;
+        cvt(c::setsockopt(sock.as_raw(), opt, val, payload, mem::size_of::<T>() as c::socklen_t))?;
         Ok(())
     }
 }
@@ -76,7 +70,7 @@ pub fn getsockopt<T: Copy>(sock: &Socket, opt: c_int, val: c_int) -> io::Result<
     unsafe {
         let mut slot: T = mem::zeroed();
         let mut len = mem::size_of::<T>() as c::socklen_t;
-        cvt(c::getsockopt(*sock.as_inner(), opt, val, &mut slot as *mut _ as *mut _, &mut len))?;
+        cvt(c::getsockopt(sock.as_raw(), opt, val, &mut slot as *mut _ as *mut _, &mut len))?;
         assert_eq!(len as usize, mem::size_of::<T>());
         Ok(slot)
     }
@@ -217,7 +211,7 @@ impl TcpStream {
         let sock = Socket::new(addr, c::SOCK_STREAM)?;
 
         let (addrp, len) = addr.into_inner();
-        cvt_r(|| unsafe { c::connect(*sock.as_inner(), addrp, len) })?;
+        cvt_r(|| unsafe { c::connect(sock.as_raw(), addrp, len) })?;
         Ok(TcpStream { inner: sock })
     }
 
@@ -273,7 +267,7 @@ impl TcpStream {
     pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
         let len = cmp::min(buf.len(), <wrlen_t>::MAX as usize) as wrlen_t;
         let ret = cvt(unsafe {
-            c::send(*self.inner.as_inner(), buf.as_ptr() as *const c_void, len, MSG_NOSIGNAL)
+            c::send(self.inner.as_raw(), buf.as_ptr() as *const c_void, len, MSG_NOSIGNAL)
         })?;
         Ok(ret as usize)
     }
@@ -288,11 +282,11 @@ impl TcpStream {
     }
 
     pub fn peer_addr(&self) -> io::Result<SocketAddr> {
-        sockname(|buf, len| unsafe { c::getpeername(*self.inner.as_inner(), buf, len) })
+        sockname(|buf, len| unsafe { c::getpeername(self.inner.as_raw(), buf, len) })
     }
 
     pub fn socket_addr(&self) -> io::Result<SocketAddr> {
-        sockname(|buf, len| unsafe { c::getsockname(*self.inner.as_inner(), buf, len) })
+        sockname(|buf, len| unsafe { c::getsockname(self.inner.as_raw(), buf, len) })
     }
 
     pub fn shutdown(&self, how: Shutdown) -> io::Result<()> {
@@ -348,7 +342,7 @@ impl fmt::Debug for TcpStream {
         }
 
         let name = if cfg!(windows) { "socket" } else { "fd" };
-        res.field(name, &self.inner.as_inner()).finish()
+        res.field(name, &self.inner.as_raw()).finish()
     }
 }
 
@@ -380,10 +374,10 @@ impl TcpListener {
 
         // Bind our new socket
         let (addrp, len) = addr.into_inner();
-        cvt(unsafe { c::bind(*sock.as_inner(), addrp, len as _) })?;
+        cvt(unsafe { c::bind(sock.as_raw(), addrp, len as _) })?;
 
         // Start listening
-        cvt(unsafe { c::listen(*sock.as_inner(), 128) })?;
+        cvt(unsafe { c::listen(sock.as_raw(), 128) })?;
         Ok(TcpListener { inner: sock })
     }
 
@@ -396,7 +390,7 @@ impl TcpListener {
     }
 
     pub fn socket_addr(&self) -> io::Result<SocketAddr> {
-        sockname(|buf, len| unsafe { c::getsockname(*self.inner.as_inner(), buf, len) })
+        sockname(|buf, len| unsafe { c::getsockname(self.inner.as_raw(), buf, len) })
     }
 
     pub fn accept(&self) -> io::Result<(TcpStream, SocketAddr)> {
@@ -453,7 +447,7 @@ impl fmt::Debug for TcpListener {
         }
 
         let name = if cfg!(windows) { "socket" } else { "fd" };
-        res.field(name, &self.inner.as_inner()).finish()
+        res.field(name, &self.inner.as_raw()).finish()
     }
 }
 
@@ -473,7 +467,7 @@ impl UdpSocket {
 
         let sock = Socket::new(addr, c::SOCK_DGRAM)?;
         let (addrp, len) = addr.into_inner();
-        cvt(unsafe { c::bind(*sock.as_inner(), addrp, len as _) })?;
+        cvt(unsafe { c::bind(sock.as_raw(), addrp, len as _) })?;
         Ok(UdpSocket { inner: sock })
     }
 
@@ -486,11 +480,11 @@ impl UdpSocket {
     }
 
     pub fn peer_addr(&self) -> io::Result<SocketAddr> {
-        sockname(|buf, len| unsafe { c::getpeername(*self.inner.as_inner(), buf, len) })
+        sockname(|buf, len| unsafe { c::getpeername(self.inner.as_raw(), buf, len) })
     }
 
     pub fn socket_addr(&self) -> io::Result<SocketAddr> {
-        sockname(|buf, len| unsafe { c::getsockname(*self.inner.as_inner(), buf, len) })
+        sockname(|buf, len| unsafe { c::getsockname(self.inner.as_raw(), buf, len) })
     }
 
     pub fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
@@ -506,7 +500,7 @@ impl UdpSocket {
         let (dstp, dstlen) = dst.into_inner();
         let ret = cvt(unsafe {
             c::sendto(
-                *self.inner.as_inner(),
+                self.inner.as_raw(),
                 buf.as_ptr() as *const c_void,
                 len,
                 MSG_NOSIGNAL,
@@ -643,14 +637,14 @@ impl UdpSocket {
     pub fn send(&self, buf: &[u8]) -> io::Result<usize> {
         let len = cmp::min(buf.len(), <wrlen_t>::MAX as usize) as wrlen_t;
         let ret = cvt(unsafe {
-            c::send(*self.inner.as_inner(), buf.as_ptr() as *const c_void, len, MSG_NOSIGNAL)
+            c::send(self.inner.as_raw(), buf.as_ptr() as *const c_void, len, MSG_NOSIGNAL)
         })?;
         Ok(ret as usize)
     }
 
     pub fn connect(&self, addr: io::Result<&SocketAddr>) -> io::Result<()> {
         let (addrp, len) = addr?.into_inner();
-        cvt_r(|| unsafe { c::connect(*self.inner.as_inner(), addrp, len) }).map(drop)
+        cvt_r(|| unsafe { c::connect(self.inner.as_raw(), addrp, len) }).map(drop)
     }
 }
 
@@ -669,6 +663,6 @@ impl fmt::Debug for UdpSocket {
         }
 
         let name = if cfg!(windows) { "socket" } else { "fd" };
-        res.field(name, &self.inner.as_inner()).finish()
+        res.field(name, &self.inner.as_raw()).finish()
     }
 }