diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl
index f048f6fe8ad4b..4f2d59b4c6632 100644
--- a/compiler/rustc_lint/messages.ftl
+++ b/compiler/rustc_lint/messages.ftl
@@ -185,6 +185,10 @@ lint_cfg_attr_no_attributes =
 
 lint_check_name_unknown_tool = unknown lint tool: `{$tool_name}`
 
+lint_closure_returning_async_block = closure returning async block can be made into an async closure
+    .label = this async block can be removed, and the closure can be turned into an async closure
+    .suggestion = turn this into an async closure
+
 lint_command_line_source = `forbid` lint level was set on command line
 
 lint_confusable_identifier_pair = found both `{$existing_sym}` and `{$sym}` as identifiers, which look alike
diff --git a/compiler/rustc_lint/src/async_closures.rs b/compiler/rustc_lint/src/async_closures.rs
new file mode 100644
index 0000000000000..33cc5738262f6
--- /dev/null
+++ b/compiler/rustc_lint/src/async_closures.rs
@@ -0,0 +1,129 @@
+use rustc_hir as hir;
+use rustc_macros::{LintDiagnostic, Subdiagnostic};
+use rustc_session::{declare_lint, declare_lint_pass};
+use rustc_span::Span;
+
+use crate::{LateContext, LateLintPass};
+
+declare_lint! {
+    /// The `closure_returning_async_block` lint detects cases where users
+    /// write a closure that returns an async block.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// #![warn(closure_returning_async_block)]
+    /// let c = |x: &str| async {};
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Using an async closure is preferable over a closure that returns an
+    /// async block, since async closures are less restrictive in how its
+    /// captures are allowed to be used.
+    ///
+    /// For example, this code does not work with a closure returning an async
+    /// block:
+    ///
+    /// ```rust,compile_fail
+    /// async fn callback(x: &str) {}
+    ///
+    /// let captured_str = String::new();
+    /// let c = move || async {
+    ///     callback(&captured_str).await;
+    /// };
+    /// ```
+    ///
+    /// But it does work with async closures:
+    ///
+    /// ```rust
+    /// #![feature(async_closure)]
+    ///
+    /// async fn callback(x: &str) {}
+    ///
+    /// let captured_str = String::new();
+    /// let c = async move || {
+    ///     callback(&captured_str).await;
+    /// };
+    /// ```
+    pub CLOSURE_RETURNING_ASYNC_BLOCK,
+    Allow,
+    "closure that returns `async {}` could be rewritten as an async closure",
+    @feature_gate = async_closure;
+}
+
+declare_lint_pass!(
+    /// Lint for potential usages of async closures and async fn trait bounds.
+    AsyncClosureUsage => [CLOSURE_RETURNING_ASYNC_BLOCK]
+);
+
+impl<'tcx> LateLintPass<'tcx> for AsyncClosureUsage {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
+        let hir::ExprKind::Closure(&hir::Closure {
+            body,
+            kind: hir::ClosureKind::Closure,
+            fn_decl_span,
+            ..
+        }) = expr.kind
+        else {
+            return;
+        };
+
+        let mut body = cx.tcx.hir().body(body).value;
+
+        // Only peel blocks that have no expressions.
+        while let hir::ExprKind::Block(&hir::Block { stmts: [], expr: Some(tail), .. }, None) =
+            body.kind
+        {
+            body = tail;
+        }
+
+        let hir::ExprKind::Closure(&hir::Closure {
+            kind:
+                hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(
+                    hir::CoroutineDesugaring::Async,
+                    hir::CoroutineSource::Block,
+                )),
+            fn_decl_span: async_decl_span,
+            ..
+        }) = body.kind
+        else {
+            return;
+        };
+
+        let deletion_span = cx.tcx.sess.source_map().span_extend_while_whitespace(async_decl_span);
+
+        cx.tcx.emit_node_span_lint(
+            CLOSURE_RETURNING_ASYNC_BLOCK,
+            expr.hir_id,
+            fn_decl_span,
+            ClosureReturningAsyncBlock {
+                async_decl_span,
+                sugg: AsyncClosureSugg {
+                    deletion_span,
+                    insertion_span: fn_decl_span.shrink_to_lo(),
+                },
+            },
+        );
+    }
+}
+
+#[derive(LintDiagnostic)]
+#[diag(lint_closure_returning_async_block)]
+struct ClosureReturningAsyncBlock {
+    #[label]
+    async_decl_span: Span,
+    #[subdiagnostic]
+    sugg: AsyncClosureSugg,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(lint_suggestion, applicability = "maybe-incorrect")]
+struct AsyncClosureSugg {
+    #[suggestion_part(code = "")]
+    deletion_span: Span,
+    #[suggestion_part(code = "async ")]
+    insertion_span: Span,
+}
diff --git a/compiler/rustc_lint/src/impl_trait_overcaptures.rs b/compiler/rustc_lint/src/impl_trait_overcaptures.rs
index d4f6d388d9fe3..0860413190c3d 100644
--- a/compiler/rustc_lint/src/impl_trait_overcaptures.rs
+++ b/compiler/rustc_lint/src/impl_trait_overcaptures.rs
@@ -11,7 +11,7 @@ use rustc_middle::ty::{
     self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor,
 };
 use rustc_session::{declare_lint, declare_lint_pass};
-use rustc_span::{sym, Span};
+use rustc_span::Span;
 
 use crate::fluent_generated as fluent;
 use crate::{LateContext, LateLintPass};
@@ -57,7 +57,7 @@ declare_lint! {
     pub IMPL_TRAIT_OVERCAPTURES,
     Allow,
     "`impl Trait` will capture more lifetimes than possibly intended in edition 2024",
-    @feature_gate = sym::precise_capturing;
+    @feature_gate = precise_capturing;
     //@future_incompatible = FutureIncompatibleInfo {
     //    reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024),
     //    reference: "<FIXME>",
@@ -91,7 +91,7 @@ declare_lint! {
     pub IMPL_TRAIT_REDUNDANT_CAPTURES,
     Warn,
     "redundant precise-capturing `use<...>` syntax on an `impl Trait`",
-    @feature_gate = sym::precise_capturing;
+    @feature_gate = precise_capturing;
 }
 
 declare_lint_pass!(
diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs
index 868a44a980a0f..b6927cf60b69e 100644
--- a/compiler/rustc_lint/src/lib.rs
+++ b/compiler/rustc_lint/src/lib.rs
@@ -41,6 +41,7 @@
 #![feature(trait_upcasting)]
 // tidy-alphabetical-end
 
+mod async_closures;
 mod async_fn_in_trait;
 pub mod builtin;
 mod context;
@@ -87,6 +88,7 @@ use rustc_hir::def_id::LocalModDefId;
 use rustc_middle::query::Providers;
 use rustc_middle::ty::TyCtxt;
 
+use async_closures::AsyncClosureUsage;
 use async_fn_in_trait::AsyncFnInTrait;
 use builtin::*;
 use deref_into_dyn_supertrait::*;
@@ -229,6 +231,7 @@ late_lint_methods!(
             MapUnitFn: MapUnitFn,
             MissingDebugImplementations: MissingDebugImplementations,
             MissingDoc: MissingDoc,
+            AsyncClosureUsage: AsyncClosureUsage,
             AsyncFnInTrait: AsyncFnInTrait,
             NonLocalDefinitions: NonLocalDefinitions::default(),
             ImplTraitOvercaptures: ImplTraitOvercaptures,
diff --git a/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs b/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs
index 445dcd41e5d52..93dd5e764c67d 100644
--- a/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs
+++ b/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs
@@ -2,7 +2,6 @@ use crate::{LateContext, LateLintPass, LintContext};
 
 use rustc_hir as hir;
 use rustc_session::{declare_lint, declare_lint_pass};
-use rustc_span::sym;
 
 declare_lint! {
     /// The `multiple_supertrait_upcastable` lint detects when an object-safe trait has multiple
@@ -30,7 +29,7 @@ declare_lint! {
     pub MULTIPLE_SUPERTRAIT_UPCASTABLE,
     Allow,
     "detect when an object-safe trait has multiple supertraits",
-    @feature_gate = sym::multiple_supertrait_upcastable;
+    @feature_gate = multiple_supertrait_upcastable;
 }
 
 declare_lint_pass!(MultipleSupertraitUpcastable => [MULTIPLE_SUPERTRAIT_UPCASTABLE]);
diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs
index 48e20a586c6d0..aa7844f40121b 100644
--- a/compiler/rustc_lint_defs/src/builtin.rs
+++ b/compiler/rustc_lint_defs/src/builtin.rs
@@ -9,7 +9,6 @@
 
 use crate::{declare_lint, declare_lint_pass, FutureIncompatibilityReason};
 use rustc_span::edition::Edition;
-use rustc_span::symbol::sym;
 
 declare_lint_pass! {
     /// Does nothing as a lint pass, but registers some `Lint`s
@@ -463,7 +462,7 @@ declare_lint! {
     pub MUST_NOT_SUSPEND,
     Allow,
     "use of a `#[must_not_suspend]` value across a yield point",
-    @feature_gate = rustc_span::symbol::sym::must_not_suspend;
+    @feature_gate = must_not_suspend;
 }
 
 declare_lint! {
@@ -1647,7 +1646,7 @@ declare_lint! {
     pub RUST_2024_INCOMPATIBLE_PAT,
     Allow,
     "detects patterns whose meaning will change in Rust 2024",
-    @feature_gate = sym::ref_pat_eat_one_layer_2024;
+    @feature_gate = ref_pat_eat_one_layer_2024;
     // FIXME uncomment below upon stabilization
     /*@future_incompatible = FutureIncompatibleInfo {
         reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024),
@@ -2695,7 +2694,7 @@ declare_lint! {
     pub FUZZY_PROVENANCE_CASTS,
     Allow,
     "a fuzzy integer to pointer cast is used",
-    @feature_gate = sym::strict_provenance;
+    @feature_gate = strict_provenance;
 }
 
 declare_lint! {
@@ -2741,7 +2740,7 @@ declare_lint! {
     pub LOSSY_PROVENANCE_CASTS,
     Allow,
     "a lossy pointer to integer cast is used",
-    @feature_gate = sym::strict_provenance;
+    @feature_gate = strict_provenance;
 }
 
 declare_lint! {
@@ -3925,7 +3924,7 @@ declare_lint! {
     pub NON_EXHAUSTIVE_OMITTED_PATTERNS,
     Allow,
     "detect when patterns of types marked `non_exhaustive` are missed",
-    @feature_gate = sym::non_exhaustive_omitted_patterns_lint;
+    @feature_gate = non_exhaustive_omitted_patterns_lint;
 }
 
 declare_lint! {
@@ -4045,7 +4044,7 @@ declare_lint! {
     pub TEST_UNSTABLE_LINT,
     Deny,
     "this unstable lint is only for testing",
-    @feature_gate = sym::test_unstable_lint;
+    @feature_gate = test_unstable_lint;
 }
 
 declare_lint! {
diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs
index b44eb25216770..f87f19e170005 100644
--- a/compiler/rustc_lint_defs/src/lib.rs
+++ b/compiler/rustc_lint_defs/src/lib.rs
@@ -865,7 +865,7 @@ macro_rules! declare_lint {
         );
     );
     ($(#[$attr:meta])* $vis: vis $NAME: ident, $Level: ident, $desc: expr,
-     $(@feature_gate = $gate:expr;)?
+     $(@feature_gate = $gate:ident;)?
      $(@future_incompatible = FutureIncompatibleInfo {
         reason: $reason:expr,
         $($field:ident : $val:expr),* $(,)*
@@ -879,7 +879,7 @@ macro_rules! declare_lint {
             desc: $desc,
             is_externally_loaded: false,
             $($v: true,)*
-            $(feature_gate: Some($gate),)?
+            $(feature_gate: Some(rustc_span::symbol::sym::$gate),)?
             $(future_incompatible: Some($crate::FutureIncompatibleInfo {
                 reason: $reason,
                 $($field: $val,)*
@@ -895,21 +895,21 @@ macro_rules! declare_lint {
 macro_rules! declare_tool_lint {
     (
         $(#[$attr:meta])* $vis:vis $tool:ident ::$NAME:ident, $Level: ident, $desc: expr
-        $(, @feature_gate = $gate:expr;)?
+        $(, @feature_gate = $gate:ident;)?
     ) => (
         $crate::declare_tool_lint!{$(#[$attr])* $vis $tool::$NAME, $Level, $desc, false $(, @feature_gate = $gate;)?}
     );
     (
         $(#[$attr:meta])* $vis:vis $tool:ident ::$NAME:ident, $Level:ident, $desc:expr,
         report_in_external_macro: $rep:expr
-        $(, @feature_gate = $gate:expr;)?
+        $(, @feature_gate = $gate:ident;)?
     ) => (
          $crate::declare_tool_lint!{$(#[$attr])* $vis $tool::$NAME, $Level, $desc, $rep $(, @feature_gate = $gate;)?}
     );
     (
         $(#[$attr:meta])* $vis:vis $tool:ident ::$NAME:ident, $Level:ident, $desc:expr,
         $external:expr
-        $(, @feature_gate = $gate:expr;)?
+        $(, @feature_gate = $gate:ident;)?
     ) => (
         $(#[$attr])*
         $vis static $NAME: &$crate::Lint = &$crate::Lint {
@@ -920,7 +920,7 @@ macro_rules! declare_tool_lint {
             report_in_external_macro: $external,
             future_incompatible: None,
             is_externally_loaded: true,
-            $(feature_gate: Some($gate),)?
+            $(feature_gate: Some(rustc_span::symbol::sym::$gate),)?
             crate_level_only: false,
             ..$crate::Lint::default_fields_for_macro()
         };
diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs
index 66aeb35aceec0..d13e38889e1d5 100644
--- a/library/std/src/lib.rs
+++ b/library/std/src/lib.rs
@@ -684,6 +684,9 @@ pub use core::{
     module_path, option_env, stringify, trace_macros,
 };
 
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+pub use crate::sys::anonymous_pipe as pipe;
+
 #[unstable(
     feature = "concat_bytes",
     issue = "87555",
diff --git a/library/std/src/process/tests.rs b/library/std/src/process/tests.rs
index 63455a274faaa..b4f6bb71c79c4 100644
--- a/library/std/src/process/tests.rs
+++ b/library/std/src/process/tests.rs
@@ -386,7 +386,7 @@ fn test_interior_nul_in_env_value_is_error() {
 fn test_creation_flags() {
     use crate::os::windows::process::CommandExt;
     use crate::sys::c::{BOOL, DWORD, INFINITE};
-    #[repr(C, packed)]
+    #[repr(C)]
     struct DEBUG_EVENT {
         pub event_code: DWORD,
         pub process_id: DWORD,
diff --git a/library/std/src/sys/anonymous_pipe/mod.rs b/library/std/src/sys/anonymous_pipe/mod.rs
new file mode 100644
index 0000000000000..118bcbaa75bf7
--- /dev/null
+++ b/library/std/src/sys/anonymous_pipe/mod.rs
@@ -0,0 +1,141 @@
+//! Module for annoymous pipe
+//!
+//! ```
+//! #![feature(anonymous_pipe)]
+//! # fn main() -> std::io::Result<()> {
+//! let (reader, writer) = std::pipe::pipe()?;
+//! # Ok(())
+//! # }
+//! ```
+
+use crate::{io, process::Stdio, sys::pipe::AnonPipe};
+
+/// Create annoymous pipe that is close-on-exec and blocking.
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+#[inline]
+pub fn pipe() -> io::Result<(PipeReader, PipeWriter)> {
+    cfg_if::cfg_if! {
+        if #[cfg(unix)] {
+            unix::pipe()
+        } else if #[cfg(windows)] {
+            windows::pipe()
+        } else {
+            Err(io::Error::UNSUPPORTED_PLATFORM)
+        }
+    }
+}
+
+/// Read end of the annoymous pipe.
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+#[derive(Debug)]
+pub struct PipeReader(AnonPipe);
+
+/// Write end of the annoymous pipe.
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+#[derive(Debug)]
+pub struct PipeWriter(AnonPipe);
+
+impl PipeReader {
+    /// Create a new [`PipeReader`] instance that shares the same underlying file description.
+    #[unstable(feature = "anonymous_pipe", issue = "127154")]
+    pub fn try_clone(&self) -> io::Result<Self> {
+        self.0.try_clone().map(Self)
+    }
+}
+
+impl PipeWriter {
+    /// Create a new [`PipeWriter`] instance that shares the same underlying file description.
+    #[unstable(feature = "anonymous_pipe", issue = "127154")]
+    pub fn try_clone(&self) -> io::Result<Self> {
+        self.0.try_clone().map(Self)
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl io::Read for &PipeReader {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        self.0.read(buf)
+    }
+    fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
+        self.0.read_vectored(bufs)
+    }
+    #[inline]
+    fn is_read_vectored(&self) -> bool {
+        self.0.is_read_vectored()
+    }
+    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
+        self.0.read_to_end(buf)
+    }
+    fn read_buf(&mut self, buf: io::BorrowedCursor<'_>) -> io::Result<()> {
+        self.0.read_buf(buf)
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl io::Read for PipeReader {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        self.0.read(buf)
+    }
+    fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
+        self.0.read_vectored(bufs)
+    }
+    #[inline]
+    fn is_read_vectored(&self) -> bool {
+        self.0.is_read_vectored()
+    }
+    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
+        self.0.read_to_end(buf)
+    }
+    fn read_buf(&mut self, buf: io::BorrowedCursor<'_>) -> io::Result<()> {
+        self.0.read_buf(buf)
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl io::Write for &PipeWriter {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        self.0.write(buf)
+    }
+    #[inline]
+    fn flush(&mut self) -> io::Result<()> {
+        Ok(())
+    }
+
+    fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
+        self.0.write_vectored(bufs)
+    }
+
+    #[inline]
+    fn is_write_vectored(&self) -> bool {
+        self.0.is_write_vectored()
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl io::Write for PipeWriter {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        self.0.write(buf)
+    }
+    #[inline]
+    fn flush(&mut self) -> io::Result<()> {
+        Ok(())
+    }
+
+    fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
+        self.0.write_vectored(bufs)
+    }
+
+    #[inline]
+    fn is_write_vectored(&self) -> bool {
+        self.0.is_write_vectored()
+    }
+}
+
+#[cfg(unix)]
+mod unix;
+
+#[cfg(windows)]
+mod windows;
+
+#[cfg(test)]
+mod tests;
diff --git a/library/std/src/sys/anonymous_pipe/tests.rs b/library/std/src/sys/anonymous_pipe/tests.rs
new file mode 100644
index 0000000000000..8f17422901d62
--- /dev/null
+++ b/library/std/src/sys/anonymous_pipe/tests.rs
@@ -0,0 +1,29 @@
+use super::*;
+use crate::io::{Read, Write};
+
+#[test]
+fn pipe_creation_and_rw() {
+    let (mut rx, mut tx) = pipe().unwrap();
+    tx.write_all(b"12345").unwrap();
+    drop(tx);
+
+    let mut s = String::new();
+    rx.read_to_string(&mut s).unwrap();
+    assert_eq!(s, "12345");
+}
+
+#[test]
+fn pipe_try_clone_and_rw() {
+    let (mut rx, mut tx) = pipe().unwrap();
+    tx.try_clone().unwrap().write_all(b"12").unwrap();
+    tx.write_all(b"345").unwrap();
+    drop(tx);
+
+    let mut s = String::new();
+    rx.try_clone().unwrap().take(3).read_to_string(&mut s).unwrap();
+    assert_eq!(s, "123");
+
+    s.clear();
+    rx.read_to_string(&mut s).unwrap();
+    assert_eq!(s, "45");
+}
diff --git a/library/std/src/sys/anonymous_pipe/unix.rs b/library/std/src/sys/anonymous_pipe/unix.rs
new file mode 100644
index 0000000000000..18252f0183d3b
--- /dev/null
+++ b/library/std/src/sys/anonymous_pipe/unix.rs
@@ -0,0 +1,107 @@
+use super::*;
+
+use crate::{
+    os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd},
+    sys::{
+        fd::FileDesc,
+        pipe::{anon_pipe, AnonPipe},
+    },
+    sys_common::{FromInner, IntoInner},
+};
+
+#[inline]
+pub(super) fn pipe() -> io::Result<(PipeReader, PipeWriter)> {
+    anon_pipe().map(|(rx, tx)| (PipeReader(rx), PipeWriter(tx)))
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl AsFd for PipeReader {
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.0.as_fd()
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl AsRawFd for PipeReader {
+    fn as_raw_fd(&self) -> RawFd {
+        self.0.as_raw_fd()
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<PipeReader> for OwnedFd {
+    fn from(pipe: PipeReader) -> Self {
+        FileDesc::into_inner(AnonPipe::into_inner(pipe.0))
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl FromRawFd for PipeReader {
+    unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
+        Self(AnonPipe::from_raw_fd(raw_fd))
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl IntoRawFd for PipeReader {
+    fn into_raw_fd(self) -> RawFd {
+        self.0.into_raw_fd()
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<PipeReader> for Stdio {
+    fn from(pipe: PipeReader) -> Self {
+        Self::from(OwnedFd::from(pipe))
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl AsFd for PipeWriter {
+    fn as_fd(&self) -> BorrowedFd<'_> {
+        self.0.as_fd()
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl AsRawFd for PipeWriter {
+    fn as_raw_fd(&self) -> RawFd {
+        self.0.as_raw_fd()
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<PipeWriter> for OwnedFd {
+    fn from(pipe: PipeWriter) -> Self {
+        FileDesc::into_inner(AnonPipe::into_inner(pipe.0))
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl FromRawFd for PipeWriter {
+    unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
+        Self(AnonPipe::from_raw_fd(raw_fd))
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl IntoRawFd for PipeWriter {
+    fn into_raw_fd(self) -> RawFd {
+        self.0.into_raw_fd()
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<PipeWriter> for Stdio {
+    fn from(pipe: PipeWriter) -> Self {
+        Self::from(OwnedFd::from(pipe))
+    }
+}
+
+fn convert_to_pipe(owned_fd: OwnedFd) -> AnonPipe {
+    AnonPipe::from_inner(FileDesc::from_inner(OwnedFd::from(owned_fd)))
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<OwnedFd> for PipeReader {
+    fn from(owned_fd: OwnedFd) -> Self {
+        Self(convert_to_pipe(owned_fd))
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<OwnedFd> for PipeWriter {
+    fn from(owned_fd: OwnedFd) -> Self {
+        Self(convert_to_pipe(owned_fd))
+    }
+}
diff --git a/library/std/src/sys/anonymous_pipe/windows.rs b/library/std/src/sys/anonymous_pipe/windows.rs
new file mode 100644
index 0000000000000..592e2f05a9ebf
--- /dev/null
+++ b/library/std/src/sys/anonymous_pipe/windows.rs
@@ -0,0 +1,113 @@
+use super::*;
+
+use crate::{
+    os::windows::io::{
+        AsHandle, AsRawHandle, BorrowedHandle, FromRawHandle, IntoRawHandle, OwnedHandle, RawHandle,
+    },
+    sys::{
+        handle::Handle,
+        pipe::{anon_pipe, AnonPipe, Pipes},
+    },
+    sys_common::{FromInner, IntoInner},
+};
+
+#[inline]
+pub(super) fn pipe() -> io::Result<(PipeReader, PipeWriter)> {
+    anon_pipe(true, false).map(|Pipes { ours, theirs }| (PipeReader(ours), PipeWriter(theirs)))
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl AsHandle for PipeReader {
+    fn as_handle(&self) -> BorrowedHandle<'_> {
+        self.0.handle().as_handle()
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl AsRawHandle for PipeReader {
+    fn as_raw_handle(&self) -> RawHandle {
+        self.0.handle().as_raw_handle()
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl FromRawHandle for PipeReader {
+    unsafe fn from_raw_handle(raw_handle: RawHandle) -> Self {
+        Self(AnonPipe::from_inner(Handle::from_raw_handle(raw_handle)))
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl IntoRawHandle for PipeReader {
+    fn into_raw_handle(self) -> RawHandle {
+        self.0.into_handle().into_raw_handle()
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<PipeReader> for OwnedHandle {
+    fn from(pipe: PipeReader) -> Self {
+        Handle::into_inner(AnonPipe::into_inner(pipe.0))
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<PipeReader> for Stdio {
+    fn from(pipe: PipeReader) -> Self {
+        Self::from(OwnedHandle::from(pipe))
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl AsHandle for PipeWriter {
+    fn as_handle(&self) -> BorrowedHandle<'_> {
+        self.0.handle().as_handle()
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl AsRawHandle for PipeWriter {
+    fn as_raw_handle(&self) -> RawHandle {
+        self.0.handle().as_raw_handle()
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl FromRawHandle for PipeWriter {
+    unsafe fn from_raw_handle(raw_handle: RawHandle) -> Self {
+        Self(AnonPipe::from_inner(Handle::from_raw_handle(raw_handle)))
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl IntoRawHandle for PipeWriter {
+    fn into_raw_handle(self) -> RawHandle {
+        self.0.into_handle().into_raw_handle()
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<PipeWriter> for OwnedHandle {
+    fn from(pipe: PipeWriter) -> Self {
+        Handle::into_inner(AnonPipe::into_inner(pipe.0))
+    }
+}
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<PipeWriter> for Stdio {
+    fn from(pipe: PipeWriter) -> Self {
+        Self::from(OwnedHandle::from(pipe))
+    }
+}
+
+fn convert_to_pipe(owned_handle: OwnedHandle) -> AnonPipe {
+    AnonPipe::from_inner(Handle::from_inner(owned_handle))
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<OwnedHandle> for PipeReader {
+    fn from(owned_handle: OwnedHandle) -> Self {
+        Self(convert_to_pipe(owned_handle))
+    }
+}
+
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+impl From<OwnedHandle> for PipeWriter {
+    fn from(owned_handle: OwnedHandle) -> Self {
+        Self(convert_to_pipe(owned_handle))
+    }
+}
diff --git a/library/std/src/sys/mod.rs b/library/std/src/sys/mod.rs
index 8aa35c40fe052..f7b41a2d4064a 100644
--- a/library/std/src/sys/mod.rs
+++ b/library/std/src/sys/mod.rs
@@ -5,6 +5,8 @@ mod pal;
 
 mod personality;
 
+#[unstable(feature = "anonymous_pipe", issue = "127154")]
+pub mod anonymous_pipe;
 pub mod backtrace;
 pub mod cmath;
 pub mod os_str;
diff --git a/library/std/src/sys/pal/unix/pipe.rs b/library/std/src/sys/pal/unix/pipe.rs
index 33db24e77e4da..8762af614f17e 100644
--- a/library/std/src/sys/pal/unix/pipe.rs
+++ b/library/std/src/sys/pal/unix/pipe.rs
@@ -9,6 +9,7 @@ use crate::sys_common::{FromInner, IntoInner};
 // Anonymous pipes
 ////////////////////////////////////////////////////////////////////////////////
 
+#[derive(Debug)]
 pub struct AnonPipe(FileDesc);
 
 pub fn anon_pipe() -> io::Result<(AnonPipe, AnonPipe)> {
@@ -46,6 +47,10 @@ pub fn anon_pipe() -> io::Result<(AnonPipe, AnonPipe)> {
 }
 
 impl AnonPipe {
+    pub fn try_clone(&self) -> io::Result<Self> {
+        self.0.duplicate().map(Self)
+    }
+
     pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
         self.0.read(buf)
     }
@@ -79,6 +84,10 @@ impl AnonPipe {
     pub fn is_write_vectored(&self) -> bool {
         self.0.is_write_vectored()
     }
+
+    pub fn as_file_desc(&self) -> &FileDesc {
+        &self.0
+    }
 }
 
 impl IntoInner<FileDesc> for AnonPipe {
diff --git a/library/std/src/sys/pal/unsupported/pipe.rs b/library/std/src/sys/pal/unsupported/pipe.rs
index d7d8f297ae586..eaa95594db0b3 100644
--- a/library/std/src/sys/pal/unsupported/pipe.rs
+++ b/library/std/src/sys/pal/unsupported/pipe.rs
@@ -3,6 +3,10 @@ use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut};
 pub struct AnonPipe(!);
 
 impl AnonPipe {
+    pub fn try_clone(&self) -> io::Result<Self> {
+        self.0
+    }
+
     pub fn read(&self, _buf: &mut [u8]) -> io::Result<usize> {
         self.0
     }
diff --git a/library/std/src/sys/pal/windows/handle.rs b/library/std/src/sys/pal/windows/handle.rs
index 3f85bb0a099a9..5aa2141571953 100644
--- a/library/std/src/sys/pal/windows/handle.rs
+++ b/library/std/src/sys/pal/windows/handle.rs
@@ -17,6 +17,7 @@ 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`
+#[derive(Debug)]
 pub struct Handle(OwnedHandle);
 
 impl Handle {
diff --git a/library/std/src/sys/pal/windows/pipe.rs b/library/std/src/sys/pal/windows/pipe.rs
index 67ef3ca82da02..6e6ba721d00fa 100644
--- a/library/std/src/sys/pal/windows/pipe.rs
+++ b/library/std/src/sys/pal/windows/pipe.rs
@@ -19,6 +19,7 @@ use crate::sys_common::{FromInner, IntoInner};
 // Anonymous pipes
 ////////////////////////////////////////////////////////////////////////////////
 
+#[derive(Debug)]
 pub struct AnonPipe {
     inner: Handle,
 }
@@ -182,7 +183,7 @@ pub fn spawn_pipe_relay(
     their_handle_inheritable: bool,
 ) -> io::Result<AnonPipe> {
     // We need this handle to live for the lifetime of the thread spawned below.
-    let source = source.duplicate()?;
+    let source = source.try_clone()?;
 
     // create a new pair of anon pipes.
     let Pipes { theirs, ours } = anon_pipe(ours_readable, their_handle_inheritable)?;
@@ -238,7 +239,8 @@ impl AnonPipe {
     pub fn into_handle(self) -> Handle {
         self.inner
     }
-    fn duplicate(&self) -> io::Result<Self> {
+
+    pub fn try_clone(&self) -> io::Result<Self> {
         self.inner.duplicate(0, false, c::DUPLICATE_SAME_ACCESS).map(|inner| AnonPipe { inner })
     }
 
diff --git a/src/librustdoc/lint.rs b/src/librustdoc/lint.rs
index 8eaca70eaff48..5a8fd3ebb9c83 100644
--- a/src/librustdoc/lint.rs
+++ b/src/librustdoc/lint.rs
@@ -66,7 +66,7 @@ where
 macro_rules! declare_rustdoc_lint {
     (
         $(#[$attr:meta])* $name: ident, $level: ident, $descr: literal $(,)?
-        $(@feature_gate = $gate:expr;)?
+        $(@feature_gate = $gate:ident;)?
     ) => {
         declare_tool_lint! {
             $(#[$attr])* pub rustdoc::$name, $level, $descr
@@ -128,7 +128,7 @@ declare_rustdoc_lint! {
     MISSING_DOC_CODE_EXAMPLES,
     Allow,
     "detects publicly-exported items without code samples in their documentation",
-    @feature_gate = rustc_span::symbol::sym::rustdoc_missing_doc_code_examples;
+    @feature_gate = rustdoc_missing_doc_code_examples;
 }
 
 declare_rustdoc_lint! {
diff --git a/tests/ui/async-await/async-closures/lint-closure-returning-async-block.rs b/tests/ui/async-await/async-closures/lint-closure-returning-async-block.rs
new file mode 100644
index 0000000000000..3e2ab8321a890
--- /dev/null
+++ b/tests/ui/async-await/async-closures/lint-closure-returning-async-block.rs
@@ -0,0 +1,21 @@
+//@ edition: 2021
+
+#![feature(async_closure)]
+#![deny(closure_returning_async_block)]
+
+fn main() {
+    let x = || async {};
+    //~^ ERROR closure returning async block can be made into an async closure
+
+    let x = || async move {};
+    //~^ ERROR closure returning async block can be made into an async closure
+
+    let x = move || async move {};
+    //~^ ERROR closure returning async block can be made into an async closure
+
+    let x = move || async {};
+    //~^ ERROR closure returning async block can be made into an async closure
+
+    let x = || {{ async {} }};
+    //~^ ERROR closure returning async block can be made into an async closure
+}
diff --git a/tests/ui/async-await/async-closures/lint-closure-returning-async-block.stderr b/tests/ui/async-await/async-closures/lint-closure-returning-async-block.stderr
new file mode 100644
index 0000000000000..4c0c4d797d8ef
--- /dev/null
+++ b/tests/ui/async-await/async-closures/lint-closure-returning-async-block.stderr
@@ -0,0 +1,67 @@
+error: closure returning async block can be made into an async closure
+  --> $DIR/lint-closure-returning-async-block.rs:7:13
+   |
+LL |     let x = || async {};
+   |             ^^ ----- this async block can be removed, and the closure can be turned into an async closure
+   |
+note: the lint level is defined here
+  --> $DIR/lint-closure-returning-async-block.rs:4:9
+   |
+LL | #![deny(closure_returning_async_block)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: turn this into an async closure
+   |
+LL -     let x = || async {};
+LL +     let x = async || {};
+   |
+
+error: closure returning async block can be made into an async closure
+  --> $DIR/lint-closure-returning-async-block.rs:10:13
+   |
+LL |     let x = || async move {};
+   |             ^^ ---------- this async block can be removed, and the closure can be turned into an async closure
+   |
+help: turn this into an async closure
+   |
+LL -     let x = || async move {};
+LL +     let x = async || {};
+   |
+
+error: closure returning async block can be made into an async closure
+  --> $DIR/lint-closure-returning-async-block.rs:13:13
+   |
+LL |     let x = move || async move {};
+   |             ^^^^^^^ ---------- this async block can be removed, and the closure can be turned into an async closure
+   |
+help: turn this into an async closure
+   |
+LL -     let x = move || async move {};
+LL +     let x = async move || {};
+   |
+
+error: closure returning async block can be made into an async closure
+  --> $DIR/lint-closure-returning-async-block.rs:16:13
+   |
+LL |     let x = move || async {};
+   |             ^^^^^^^ ----- this async block can be removed, and the closure can be turned into an async closure
+   |
+help: turn this into an async closure
+   |
+LL -     let x = move || async {};
+LL +     let x = async move || {};
+   |
+
+error: closure returning async block can be made into an async closure
+  --> $DIR/lint-closure-returning-async-block.rs:19:13
+   |
+LL |     let x = || {{ async {} }};
+   |             ^^    ----- this async block can be removed, and the closure can be turned into an async closure
+   |
+help: turn this into an async closure
+   |
+LL -     let x = || {{ async {} }};
+LL +     let x = async || {{ {} }};
+   |
+
+error: aborting due to 5 previous errors
+