From 8cafe4c32c7163465602948f0c31e97c7e8a40bf Mon Sep 17 00:00:00 2001 From: Valentyn Kit Date: Sun, 30 Aug 2026 20:38:11 +0300 Subject: [PATCH 01/11] std: add `fs::rename_noreplace` Unlike `fs::rename`, the destination is never overwritten, returning `ErrorKind::AlreadyExists` if `to` already exists. Platform-specific APIs used: `renameat2` with `RENAME_NOREPLACE` flag on `Linux` and `Android`, `renamex_np` with `RENAME_EXCL` on `Apple`, and `MoveFileExW` without the `MOVEFILE_REPLACE_EXISTING` flag on `Windows`. On other Unix platforms, and on Linux, Android and Apple when the kernel or the filesystem doesn't support it, `link` followed by `unlink` is used instead. Platforms with neither return `Unsupported`. --- library/std/src/fs.rs | 67 +++++++++++++++++++++++++++ library/std/src/sys/fs/hermit.rs | 4 ++ library/std/src/sys/fs/mod.rs | 4 ++ library/std/src/sys/fs/motor.rs | 4 ++ library/std/src/sys/fs/solid.rs | 4 ++ library/std/src/sys/fs/uefi.rs | 4 ++ library/std/src/sys/fs/unix.rs | 60 +++++++++++++++++++++++- library/std/src/sys/fs/unsupported.rs | 4 ++ library/std/src/sys/fs/vexos.rs | 4 +- library/std/src/sys/fs/windows.rs | 30 +++++++++--- 10 files changed, 176 insertions(+), 9 deletions(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 65b8ed634bc05..f040b35013c7e 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -3078,6 +3078,73 @@ pub fn rename, Q: AsRef>(from: P, to: Q) -> io::Result<()> fs_imp::rename(from.as_ref(), to.as_ref()) } +/// Renames a file or directory to a new name, returning [`AlreadyExists`] if `to` already +/// exists. +/// +/// Unlike [`fs::rename`], the destination is never overwritten. The check happens as part of the +/// same operation, which avoids check-then-rename races, but does not guarantee crash safety. +/// +/// This will not work if the new name is on a different mount point. +/// +/// # Platform-specific behavior +/// +/// This function currently corresponds to: +/// +/// * [renameat2] with `RENAME_NOREPLACE` on Linux and Android +/// * `renamex_np` with `RENAME_EXCL` on Apple platforms +/// * [MoveFileExW] without the `MOVEFILE_REPLACE_EXISTING` flag, with a fallback to +/// `SetFileInformationByHandle`, on Windows +/// * `link` followed by `unlink` on other Unix platforms and on WASI +/// * Otherwise, this function returns [`Unsupported`] +/// +/// The `link` and `unlink` fallback is also used at runtime on Linux, Android and Apple platforms +/// when the kernel or the filesystem does not support the flag, so which of these applies can +/// differ between filesystems on the same machine. +/// +/// Note that, this [may change in the future][changes]. +/// +/// [changes]: io#platform-specific-behavior +/// [renameat2]: https://man7.org/linux/man-pages/man2/renameat2.2.html +/// [MoveFileExW]: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefileexw +/// [`AlreadyExists`]: crate::io::ErrorKind::AlreadyExists +/// [`Unsupported`]: crate::io::ErrorKind::Unsupported +/// [`fs::rename`]: crate::fs::rename +/// +/// # Errors +/// +/// This function will return an error in the following situations, but is not +/// limited to just these cases: +/// +/// * `from` does not exist. +/// * The user lacks permissions to view contents. +/// * `from` and `to` are on separate filesystems. +/// * `to` already exists. +/// * The platform does not support this operation. +/// +/// # Examples +/// +/// ```no_run +/// #![feature(rename_noreplace)] +/// use std::fs; +/// use std::io::ErrorKind; +/// +/// fn main() -> std::io::Result<()> { +/// // Renames a.txt to b.txt, b.txt does not exist yet. +/// fs::rename_noreplace("a.txt", "b.txt")?; +/// +/// // b.txt exists now, so this fails instead of silently overwriting it. +/// let err = fs::rename_noreplace("c.txt", "b.txt").unwrap_err(); +/// assert_eq!(err.kind(), ErrorKind::AlreadyExists); +/// Ok(()) +/// } +/// ``` +#[doc(alias = "renameat2", alias = "renamex_np", alias = "MoveFileEx", alias = "RENAME_NOREPLACE")] +#[unstable(feature = "rename_noreplace", issue = "161427")] +#[cfg_attr(not(test), rustc_diagnostic_item = "fs_rename_noreplace")] +pub fn rename_noreplace, Q: AsRef>(from: P, to: Q) -> io::Result<()> { + fs_imp::rename_noreplace(from.as_ref(), to.as_ref()) +} + /// Copies the contents of one file to another. This function will also /// copy the permission bits of the original file to the destination file. /// diff --git a/library/std/src/sys/fs/hermit.rs b/library/std/src/sys/fs/hermit.rs index 33feee4fc1c97..9751b0a3a0ca7 100644 --- a/library/std/src/sys/fs/hermit.rs +++ b/library/std/src/sys/fs/hermit.rs @@ -594,6 +594,10 @@ pub fn rename(_old: &Path, _new: &Path) -> io::Result<()> { unsupported() } +pub fn rename_noreplace(_old: &Path, _new: &Path) -> io::Result<()> { + unsupported() +} + pub fn set_perm(_p: &Path, _perm: FilePermissions) -> io::Result<()> { unsupported() } diff --git a/library/std/src/sys/fs/mod.rs b/library/std/src/sys/fs/mod.rs index b2666eb2a3da9..059ba6c046076 100644 --- a/library/std/src/sys/fs/mod.rs +++ b/library/std/src/sys/fs/mod.rs @@ -78,6 +78,10 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> { with_native_path(old, &|old| with_native_path(new, &|new| imp::rename(old, new))) } +pub fn rename_noreplace(old: &Path, new: &Path) -> io::Result<()> { + with_native_path(old, &|old| with_native_path(new, &|new| imp::rename_noreplace(old, new))) +} + pub fn remove_dir(path: &Path) -> io::Result<()> { with_native_path(path, &imp::rmdir) } diff --git a/library/std/src/sys/fs/motor.rs b/library/std/src/sys/fs/motor.rs index 938d1537790a1..d4eb30f518fe0 100644 --- a/library/std/src/sys/fs/motor.rs +++ b/library/std/src/sys/fs/motor.rs @@ -324,6 +324,10 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> { moto_rt::fs::rename(old, new).map_err(map_motor_error) } +pub fn rename_noreplace(_old: &Path, _new: &Path) -> io::Result<()> { + unsupported() +} + pub fn rmdir(path: &Path) -> io::Result<()> { let path = path.to_str().ok_or(io::Error::from(io::ErrorKind::InvalidFilename))?; moto_rt::fs::rmdir(path).map_err(map_motor_error) diff --git a/library/std/src/sys/fs/solid.rs b/library/std/src/sys/fs/solid.rs index bd963b1d2f038..b1d2acdc2b79c 100644 --- a/library/std/src/sys/fs/solid.rs +++ b/library/std/src/sys/fs/solid.rs @@ -530,6 +530,10 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> { Ok(()) } +pub fn rename_noreplace(_old: &Path, _new: &Path) -> io::Result<()> { + unsupported() +} + pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> { // Solid does not support symlinks set_perm_nofollow(p, perm) diff --git a/library/std/src/sys/fs/uefi.rs b/library/std/src/sys/fs/uefi.rs index 08473e245cc8e..2d14a92a6b8c9 100644 --- a/library/std/src/sys/fs/uefi.rs +++ b/library/std/src/sys/fs/uefi.rs @@ -474,6 +474,10 @@ pub fn rename(old: &Path, new: &Path) -> io::Result<()> { f.set_file_info(new_info) } +pub fn rename_noreplace(_old: &Path, _new: &Path) -> io::Result<()> { + unsupported() +} + pub fn set_perm(p: &Path, perm: FilePermissions) -> io::Result<()> { // UEFI does not support symlinks set_perm_nofollow(p, perm) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 46d5d33d23ac2..d5e8db4e9030a 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -60,7 +60,7 @@ use crate::sys::fd::FileDesc; pub use crate::sys::fs::common::exists; use crate::sys::helpers::run_path_with_cstr; use crate::sys::time::SystemTime; -#[cfg(all(target_os = "linux", target_env = "gnu"))] +#[cfg(any(target_os = "linux", target_os = "android"))] use crate::sys::weak::syscall; #[cfg(target_os = "android")] use crate::sys::weak::weak; @@ -1885,6 +1885,59 @@ pub fn rename(old: &CStr, new: &CStr) -> io::Result<()> { cvt(unsafe { libc::rename(old.as_ptr(), new.as_ptr()) }).map(|_| ()) } +#[cfg(any(target_os = "linux", target_os = "android"))] +pub fn rename_noreplace(old: &CStr, new: &CStr) -> io::Result<()> { + syscall!( + fn renameat2( + olddirfd: c_int, + oldpath: *const libc::c_char, + newdirfd: c_int, + newpath: *const libc::c_char, + flags: libc::c_uint, + ) -> c_int; + ); + + // `RENAME_NOREPLACE` is `c_uint` on linux and `c_int` on Android, so casting is needed here. + let flags = libc::RENAME_NOREPLACE as libc::c_uint; + let res = cvt(unsafe { + renameat2(libc::AT_FDCWD, old.as_ptr(), libc::AT_FDCWD, new.as_ptr(), flags) + }); + + match res { + Ok(_) => Ok(()), + Err(err) => match err.raw_os_error() { + // The kernel predates `renameat2`. + Some(libc::ENOSYS) => link_then_unlink(old, new), + // The filesystem does not support `RENAME_NOREPLACE`. + // `EINVAL` also covers misuse, so report it unchanged unless the fallback found `new`. + Some(libc::EINVAL) => match link_then_unlink(old, new) { + Err(fallback) if fallback.raw_os_error() != Some(libc::EEXIST) => Err(err), + res => res, + }, + _ => Err(err), + }, + } +} + +#[cfg(target_vendor = "apple")] +pub fn rename_noreplace(old: &CStr, new: &CStr) -> io::Result<()> { + let res = cvt(unsafe { libc::renamex_np(old.as_ptr(), new.as_ptr(), libc::RENAME_EXCL) }); + match res { + Ok(_) => Ok(()), + Err(err) => match err.raw_os_error() { + // The filesystem does not support `RENAME_EXCL`. + // Unlike Linux, Darwin keeps `EINVAL` for misuse only. + Some(libc::ENOTSUP) => link_then_unlink(old, new), + _ => Err(err), + }, + } +} + +#[cfg(not(any(target_os = "linux", target_os = "android", target_vendor = "apple")))] +pub fn rename_noreplace(old: &CStr, new: &CStr) -> io::Result<()> { + link_then_unlink(old, new) +} + pub fn set_perm(p: &CStr, perm: FilePermissions) -> io::Result<()> { cvt_r(|| unsafe { libc::chmod(p.as_ptr(), perm.mode) }).map(|_| ()) } @@ -2094,6 +2147,11 @@ pub fn canonicalize(path: &CStr) -> io::Result { }))) } +fn link_then_unlink(old: &CStr, new: &CStr) -> io::Result<()> { + link(old, new)?; + unlink(old) +} + fn open_from(from: &Path) -> io::Result<(crate::fs::File, crate::fs::Metadata)> { use crate::fs::File; use crate::sys::fs::common::NOT_FILE_ERROR; diff --git a/library/std/src/sys/fs/unsupported.rs b/library/std/src/sys/fs/unsupported.rs index 512af27401dfe..b7aa0871a8088 100644 --- a/library/std/src/sys/fs/unsupported.rs +++ b/library/std/src/sys/fs/unsupported.rs @@ -309,6 +309,10 @@ pub fn rename(_old: &Path, _new: &Path) -> io::Result<()> { unsupported() } +pub fn rename_noreplace(_old: &Path, _new: &Path) -> io::Result<()> { + unsupported() +} + pub fn set_perm(_p: &Path, perm: FilePermissions) -> io::Result<()> { match perm.0 {} } diff --git a/library/std/src/sys/fs/vexos.rs b/library/std/src/sys/fs/vexos.rs index 1357787c44c77..d9fc8fd26e8b2 100644 --- a/library/std/src/sys/fs/vexos.rs +++ b/library/std/src/sys/fs/vexos.rs @@ -12,8 +12,8 @@ use crate::sys::{unsupported, unsupported_err}; #[path = "unsupported.rs"] mod unsupported_fs; pub use unsupported_fs::{ - Dir, DirBuilder, FileTimes, canonicalize, link, readlink, remove_dir_all, rename, rmdir, - symlink, unlink, + Dir, DirBuilder, FileTimes, canonicalize, link, readlink, remove_dir_all, rename, + rename_noreplace, rmdir, symlink, unlink, }; /// VEXos file descriptor. diff --git a/library/std/src/sys/fs/windows.rs b/library/std/src/sys/fs/windows.rs index 399c0749bc21e..479d3655db369 100644 --- a/library/std/src/sys/fs/windows.rs +++ b/library/std/src/sys/fs/windows.rs @@ -1302,8 +1302,13 @@ pub fn unlink(path: &WCStr) -> io::Result<()> { } } -pub fn rename(old: &WCStr, new: &WCStr) -> io::Result<()> { - if unsafe { c::MoveFileExW(old.as_ptr(), new.as_ptr(), c::MOVEFILE_REPLACE_EXISTING) } == 0 { +fn rename_inner( + old: &WCStr, + new: &WCStr, + move_flags: c::MOVE_FILE_FLAGS, + rename_flags: u32, +) -> io::Result<()> { + if unsafe { c::MoveFileExW(old.as_ptr(), new.as_ptr(), move_flags) } == 0 { let err = api::get_last_error(); // if `MoveFileExW` fails with ERROR_ACCESS_DENIED then try to move // the file while ignoring the readonly attribute. @@ -1335,10 +1340,8 @@ pub fn rename(old: &WCStr, new: &WCStr) -> io::Result<()> { return Err(io::ErrorKind::OutOfMemory.into()); } - (&raw mut (*file_rename_info).Anonymous).write(c::FILE_RENAME_INFO_0 { - Flags: c::FILE_RENAME_FLAG_REPLACE_IF_EXISTS - | c::FILE_RENAME_FLAG_POSIX_SEMANTICS, - }); + (&raw mut (*file_rename_info).Anonymous) + .write(c::FILE_RENAME_INFO_0 { Flags: rename_flags }); (&raw mut (*file_rename_info).RootDirectory).write(ptr::null_mut()); // Don't include the NULL in the size @@ -1373,6 +1376,21 @@ pub fn rename(old: &WCStr, new: &WCStr) -> io::Result<()> { Ok(()) } +pub fn rename(old: &WCStr, new: &WCStr) -> io::Result<()> { + rename_inner( + old, + new, + c::MOVEFILE_REPLACE_EXISTING, + c::FILE_RENAME_FLAG_REPLACE_IF_EXISTS | c::FILE_RENAME_FLAG_POSIX_SEMANTICS, + ) +} + +pub fn rename_noreplace(old: &WCStr, new: &WCStr) -> io::Result<()> { + // Without `MOVEFILE_REPLACE_EXISTING`, `MoveFileExW` fails if the destination + // exists. + rename_inner(old, new, 0, c::FILE_RENAME_FLAG_POSIX_SEMANTICS) +} + pub fn rmdir(p: &WCStr) -> io::Result<()> { cvt(unsafe { c::RemoveDirectoryW(p.as_ptr()) })?; Ok(()) From f09e0ef7eabceadb32c2e9a0a9ec95a40380cece Mon Sep 17 00:00:00 2001 From: Valentyn Kit Date: Sun, 30 Aug 2026 21:54:55 +0300 Subject: [PATCH 02/11] std: add tests for `fs::rename_noreplace` --- library/std/src/fs/tests.rs | 84 +++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 250109f277dab..98d8708b5ad86 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -2647,6 +2647,90 @@ fn test_rename_symlink() { assert_eq!(fs::read_link(&dest).unwrap().as_path(), not_exist); } +#[test] +fn test_rename_noreplace() { + let tmpdir = tmpdir(); + let source_path = tmpdir.join("source_file.txt"); + let target_path = tmpdir.join("target_file.txt"); + + fs::write(&source_path, b"source hello world").unwrap(); + + fs::rename_noreplace(&source_path, &target_path).unwrap(); + assert!(!source_path.exists()); + assert_eq!(fs::read(&target_path).unwrap(), b"source hello world"); +} + +#[test] +fn test_rename_noreplace_existing_file() { + let tmpdir = tmpdir(); + let source_path = tmpdir.join("source_file.txt"); + let target_path = tmpdir.join("target_file.txt"); + + fs::write(&source_path, b"source hello world").unwrap(); + fs::write(&target_path, b"target hello world").unwrap(); + + let err = fs::rename_noreplace(&source_path, &target_path).unwrap_err(); + assert_eq!(err.kind(), ErrorKind::AlreadyExists); + // Make sure the failed rename left both files untouched. + assert_eq!(fs::read(&source_path).unwrap(), b"source hello world"); + assert_eq!(fs::read(&target_path).unwrap(), b"target hello world"); +} + +#[test] +fn test_rename_noreplace_directory_to_empty_directory() { + // Renaming a directory over an existing empty directory should fail; + // plain `rename` would succeed here. + let tmpdir = tmpdir(); + let source_path = tmpdir.join("source_directory"); + let target_path = tmpdir.join("target_directory"); + + fs::create_dir(&source_path).unwrap(); + fs::create_dir(&target_path).unwrap(); + + let err = fs::rename_noreplace(&source_path, &target_path).unwrap_err(); + assert_matches!( + err.kind(), + // A native no-replace rename returns `AlreadyExists`. The `link`/`unlink` + // fallback cannot rename directories and returns `PermissionDenied`. + ErrorKind::AlreadyExists | ErrorKind::PermissionDenied, + "Expected AlreadyExists or PermissionDenied error, got {err}" + ); +} + +#[test] +#[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple", windows))] +fn test_rename_noreplace_directory() { + // The `link`/`unlink` fallback cannot move directories, so only platforms + // with a native no-replace rename are tested. + let tmpdir = tmpdir(); + let source_path = tmpdir.join("source_directory"); + let target_path = tmpdir.join("target_directory"); + + fs::create_dir(&source_path).unwrap(); + fs::write(source_path.join("file.txt"), b"hello world").unwrap(); + + fs::rename_noreplace(&source_path, &target_path).unwrap(); + assert!(!source_path.exists()); + assert_eq!(fs::read(target_path.join("file.txt")).unwrap(), b"hello world"); +} + +#[test] +fn test_rename_noreplace_symlink() { + let tmpdir = tmpdir(); + if !got_symlink_permission(&tmpdir) { + return; + }; + + let original = tmpdir.join("original"); + let dest = tmpdir.join("dest"); + let not_exist = Path::new("does not exist"); + + symlink_file(not_exist, &original).unwrap(); + fs::rename_noreplace(&original, &dest).unwrap(); + // Make sure that renaming `original` to `dest` preserves the symlink. + assert_eq!(fs::read_link(&dest).unwrap().as_path(), not_exist); +} + #[test] #[cfg(windows)] #[cfg_attr( From d0d58b4a64468599732faa682006113a386e041c Mon Sep 17 00:00:00 2001 From: Valentyn Kit Date: Sun, 27 Sep 2026 21:42:02 +0300 Subject: [PATCH 03/11] std: clarify rename_noreplace crash guarantees Co-authored-by: Josh Triplett --- library/std/src/fs.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index f040b35013c7e..7c833f76f3929 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -3082,7 +3082,10 @@ pub fn rename, Q: AsRef>(from: P, to: Q) -> io::Result<()> /// exists. /// /// Unlike [`fs::rename`], the destination is never overwritten. The check happens as part of the -/// same operation, which avoids check-then-rename races, but does not guarantee crash safety. +/// same operation, which avoids check-then-rename races. +/// +/// If the system crashes in the middle, it may be possible to end up with both copies of the file +/// existing, but it should not be possible to end up with zero copies, or with a half-written file. /// /// This will not work if the new name is on a different mount point. /// From 7b7ed95383a3f402319717c72d751c80d35d300f Mon Sep 17 00:00:00 2001 From: Valentyn Kit Date: Sun, 27 Sep 2026 22:46:35 +0300 Subject: [PATCH 04/11] std: take plain u32 move flags on Windows windows-bindgen no longer generates the `MOVE_FILE_FLAGS` alias, so `u32` is used instead. --- library/std/src/sys/fs/windows.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/library/std/src/sys/fs/windows.rs b/library/std/src/sys/fs/windows.rs index 479d3655db369..af909bc403590 100644 --- a/library/std/src/sys/fs/windows.rs +++ b/library/std/src/sys/fs/windows.rs @@ -1302,12 +1302,7 @@ pub fn unlink(path: &WCStr) -> io::Result<()> { } } -fn rename_inner( - old: &WCStr, - new: &WCStr, - move_flags: c::MOVE_FILE_FLAGS, - rename_flags: u32, -) -> io::Result<()> { +fn rename_inner(old: &WCStr, new: &WCStr, move_flags: u32, rename_flags: u32) -> io::Result<()> { if unsafe { c::MoveFileExW(old.as_ptr(), new.as_ptr(), move_flags) } == 0 { let err = api::get_last_error(); // if `MoveFileExW` fails with ERROR_ACCESS_DENIED then try to move From 2ad82113f8c529a3207e990763e39675900fcdf2 Mon Sep 17 00:00:00 2001 From: ywxt Date: Tue, 29 Sep 2026 17:42:22 +0800 Subject: [PATCH 05/11] vectorized vec::dedup pre-scan --- library/alloc/src/vec/mod.rs | 163 +++++++++++++++++++- library/alloctests/benches/vec.rs | 247 +++++++++++++++++------------- library/alloctests/tests/vec.rs | 88 +++++++++++ 3 files changed, 390 insertions(+), 108 deletions(-) diff --git a/library/alloc/src/vec/mod.rs b/library/alloc/src/vec/mod.rs index 4537e50e634f2..ff2895e2882bf 100644 --- a/library/alloc/src/vec/mod.rs +++ b/library/alloc/src/vec/mod.rs @@ -2717,7 +2717,24 @@ impl Vec { if first_duplicate_idx == len { return; } + // SAFETY: first_duplicate_idx always in range [1..len) + unsafe { + self.dedup_tail(same_bucket, first_duplicate_idx); + } + } + /// Continues deduplication after the first duplicate is found. + /// + /// The function treats `self[first_duplicate]` as the first duplicated + /// element and drops it. + /// + /// # Safety + /// + /// `1 <= first_duplicate < self.len()` + unsafe fn dedup_tail(&mut self, mut same_bucket: F, first_duplicate: usize) + where + F: FnMut(&mut T, &mut T) -> bool, + { /* INVARIANT: vec.len() > read > write > write-1 >= 0 */ struct FillGapOnDrop<'a, T, A: core::alloc::Allocator> { /* Offset of the element we want to check if it is duplicate */ @@ -2764,16 +2781,20 @@ impl Vec { } } + let start = self.as_mut_ptr(); + let len = self.len(); + /* Drop items while going through Vec, it should be more efficient than * doing slice partition_dedup + truncate */ // Construct gap first and then drop item to avoid memory corruption if `T::drop` panics. let mut gap = - FillGapOnDrop { read: first_duplicate_idx + 1, write: first_duplicate_idx, vec: self }; - // SAFETY: we checked that first_duplicate_idx in bounds before. + FillGapOnDrop { read: first_duplicate + 1, write: first_duplicate, vec: self }; + + // SAFETY: By this function's safety contract, `first_duplicate < self.len()`. // If drop panics, `gap` would remove this item without drop. unsafe { - ptr::drop_in_place(start.add(first_duplicate_idx)); + ptr::drop_in_place(start.add(first_duplicate)); } // SAFETY: Because of the invariant, read_ptr, prev_ptr and write_ptr @@ -3792,8 +3813,142 @@ impl Vec { #[stable(feature = "rust1", since = "1.0.0")] #[inline] pub fn dedup(&mut self) { - self.dedup_by(|a, b| a == b) + // Vectorize the process of finding first duplicate if possible + if let Some(chunk_size) = dedup_simd_chunk_size::() { + // Unlike dedup_by, we don't guarantee the order + // and time in which we call `PartialEq::eq`. + let Some(first_duplicate) = find_first_duplicate(self.as_slice(), chunk_size) else { + return; + }; + + // SAFETY: `1 <= first_duplicate < self.len()` + unsafe { + self.dedup_tail(|a, b| a == b, first_duplicate); + } + } else { + self.dedup_by(|a, b| a == b); + } + } +} + +const fn dedup_simd_chunk_size() -> Option { + // The minimal SIMD width on supported platforms + #[allow(unused)] + const SIMD_WIDTH: usize = 16; + let size = size_of::(); + + // We only have SIMD implementations for these element sizes. + if !matches!(size, 1 | 2 | 4 | 8) { + return None; + } + + let simd_width: Option = cfg_select! { + all(target_arch = "arm", target_feature = "neon") => { + // Packed 64-bit integer lane equality is unavailable on arm neon + if size == 8 { None } else { Some(SIMD_WIDTH) } + } + + all(target_arch = "aarch64", target_feature = "neon") => { + Some(SIMD_WIDTH) + } + + all( + any(target_arch = "x86", target_arch = "x86_64"), + target_feature = "sse4.1" + ) => Some(SIMD_WIDTH), + + all( + any(target_arch = "x86", target_arch = "x86_64"), + target_feature = "sse2" + ) => { + // Packed 64-bit integer lane equality is only available on sse4.1 + if size == 8 { None } else { Some(SIMD_WIDTH) } + } + + all( + any(target_arch = "riscv32", target_arch = "riscv64"), + target_feature = "v" + ) => Some(SIMD_WIDTH), + + all( + any(target_arch = "loongarch32", target_arch = "loongarch64"), + target_feature = "lsx" + ) => Some(SIMD_WIDTH), + + _ => None, + }; + + if let Some(simd_width) = simd_width { Some(simd_width * 2 / size) } else { None } +} + +fn find_first_duplicate(v: &[T], chunk_size: usize) -> Option { + let len = v.len(); + if len <= 1 { + return None; + } + + let mut i = 1; + + // The scalar fallback path if the length is not long enough + if len < chunk_size + 1 { + while i < len { + // It keeps the same comparison order as `dedup_tail` + // + // SAFETY: i always in range [1..len) + if unsafe { v.get_unchecked(i) == v.get_unchecked(i - 1) } { + return Some(i); + } + i += 1; + } + return None; + } + + /// # Safety + /// - `start > 0` and `start + chunk_size <= v.len()` + /// - `0 < chunk_size <= 32` + #[inline(always)] + unsafe fn find_duplicate( + v: &[T], + start: usize, + chunk_size: usize, + ) -> Option { + let mut mask: u32 = 0; + debug_assert!( + u32::BITS as usize >= chunk_size, + "the bit width of mask must be greater than or equal to chunk_size" + ); + for j in 0..chunk_size { + // SAFETY: start + j always in range [1..v.len()) + let eq = unsafe { v.get_unchecked(start + j) == v.get_unchecked(start + j - 1) }; + mask |= (eq as u32) << j; + } + if mask != 0 { + return Some(start + mask.trailing_zeros() as usize); + } + None } + + // This loop performs chunk_size comparisons in parallel without early exit, + // which is what allows for SIMD optimization. + while i + chunk_size <= len { + // SAFETY: `i >= 1` and `i + chunk_size <= len` + if let Some(duplicate) = unsafe { find_duplicate(v, i, chunk_size) } { + return Some(duplicate); + } + i += chunk_size; + } + + // This may check the same elements multiple times. + // However we don't guarantee `PartialEq::eq` is only called once per pair. + if i < len { + let start = len - chunk_size; + // SAFETY: `len - chunk_size >= 1` and `start + chunk_size <= len` + if let Some(duplicate) = unsafe { find_duplicate(v, start, chunk_size) } { + return Some(duplicate); + } + } + + None } //////////////////////////////////////////////////////////////////////////////// diff --git a/library/alloctests/benches/vec.rs b/library/alloctests/benches/vec.rs index 656164da72084..a4e151ed42bf7 100644 --- a/library/alloctests/benches/vec.rs +++ b/library/alloctests/benches/vec.rs @@ -688,137 +688,176 @@ fn bench_dedup_slice_truncate(b: &mut Bencher, sz: usize) { }); } -// Measures performance of Vec::dedup on random data. -fn bench_vec_dedup_random(b: &mut Bencher, sz: usize) { - let mut template = vec![0u32; sz]; - b.bytes = size_of_val(template.as_slice()) as u64; - random_sorted_fill(0x43, &mut template); - - let mut vec = template.clone(); - b.iter(|| { - let vec = black_box(&mut vec); - vec.dedup(); - black_box(vec.first()); - let vec = black_box(vec); - vec.clear(); - vec.extend_from_slice(&template); - }); -} - -// Measures performance of Vec::dedup when there is no items removed -fn bench_vec_dedup_none(b: &mut Bencher, sz: usize) { - let mut template = vec![0u32; sz]; - b.bytes = size_of_val(template.as_slice()) as u64; - template.chunks_exact_mut(2).for_each(|w| { - w[0] = black_box(0); - w[1] = black_box(5); - }); - - let mut vec = template.clone(); - b.iter(|| { - let vec = black_box(&mut vec); - vec.dedup(); - black_box(vec.first()); - // Unlike other benches of `dedup` - // this doesn't reinitialize vec - // because we measure how efficient dedup is - // when no memory written - }); -} - -// Measures performance of Vec::dedup when there is all items removed -fn bench_vec_dedup_all(b: &mut Bencher, sz: usize) { - let mut template = vec![0u32; sz]; - b.bytes = size_of_val(template.as_slice()) as u64; - template.iter_mut().for_each(|w| { - *w = black_box(0); - }); - - let mut vec = template.clone(); - b.iter(|| { - let vec = black_box(&mut vec); - vec.dedup(); - black_box(vec.first()); - let vec = black_box(vec); - vec.clear(); - vec.extend_from_slice(&template); - }); -} - #[bench] fn bench_dedup_slice_truncate_100(b: &mut Bencher) { bench_dedup_slice_truncate(b, 100); } -#[bench] -fn bench_dedup_random_100(b: &mut Bencher) { - bench_vec_dedup_random(b, 100); -} - -#[bench] -fn bench_dedup_none_100(b: &mut Bencher) { - bench_vec_dedup_none(b, 100); -} - -#[bench] -fn bench_dedup_all_100(b: &mut Bencher) { - bench_vec_dedup_all(b, 100); -} - #[bench] fn bench_dedup_slice_truncate_1000(b: &mut Bencher) { bench_dedup_slice_truncate(b, 1000); } #[bench] -fn bench_dedup_random_1000(b: &mut Bencher) { - bench_vec_dedup_random(b, 1000); +fn bench_dedup_slice_truncate_10000(b: &mut Bencher) { + bench_dedup_slice_truncate(b, 10000); } - #[bench] -fn bench_dedup_none_1000(b: &mut Bencher) { - bench_vec_dedup_none(b, 1000); +fn bench_dedup_slice_truncate_100000(b: &mut Bencher) { + bench_dedup_slice_truncate(b, 100000); } -#[bench] -fn bench_dedup_all_1000(b: &mut Bencher) { - bench_vec_dedup_all(b, 1000); +#[derive(Clone, Copy)] +enum DedupDist { + Random, + None, + All, } -#[bench] -fn bench_dedup_slice_truncate_10000(b: &mut Bencher) { - bench_dedup_slice_truncate(b, 10000); +trait DedupBenchValue { + fn bench_bytes(&self) -> usize; } -#[bench] -fn bench_dedup_random_10000(b: &mut Bencher) { - bench_vec_dedup_random(b, 10000); + +macro_rules! dedup_bench_value_by_size { + ($($ty:ty),+ $(,)?) => { + $( + impl DedupBenchValue for $ty { + fn bench_bytes(&self) -> usize { + size_of_val(self) + } + } + )+ + }; } -#[bench] -fn bench_dedup_none_10000(b: &mut Bencher) { - bench_vec_dedup_none(b, 10000); +dedup_bench_value_by_size!(u8, u16, u32, u64, [u64; 4]); + +impl DedupBenchValue for String { + fn bench_bytes(&self) -> usize { + self.len() + } } -#[bench] -fn bench_dedup_all_10000(b: &mut Bencher) { - bench_vec_dedup_all(b, 10000); +fn bench_vec_dedup( + b: &mut Bencher, + dist: DedupDist, + sz: usize, + make: impl Fn(u32) -> T, +) { + let template: Vec = match dist { + DedupDist::Random => { + let mut seeds = vec![0u32; sz]; + random_sorted_fill(0x43, &mut seeds); + let mut v: Vec = seeds.into_iter().map(make).collect(); + v.sort(); + v + } + DedupDist::None => { + (0..sz).map(|i| make(black_box(if i % 2 == 0 { 0 } else { 5 }))).collect() + } + DedupDist::All => (0..sz).map(|_| make(black_box(0))).collect(), + }; + b.bytes = template.iter().map(DedupBenchValue::bench_bytes).sum::() as u64; + let mut vec = template.clone(); + b.iter(|| { + let vec = black_box(&mut vec); + vec.dedup(); + black_box(vec.first()); + // Unlike other benches of `dedup` + // `DedupDist::None` doesn't reinitialize vec + // because we measure how efficient dedup is + // when no memory written + if !matches!(dist, DedupDist::None) { + black_box(vec).clone_from(&template); + } + }); } -#[bench] -fn bench_dedup_slice_truncate_100000(b: &mut Bencher) { - bench_dedup_slice_truncate(b, 100000); +fn dedup_gen_string(x: u32) -> String { + const DIGITS: usize = 10; + let mut s = String::with_capacity(LEN); + s.extend(std::iter::repeat_n('p', LEN - DIGITS)); + s.push_str(&format!("{x:0DIGITS$}")); + s } -#[bench] -fn bench_dedup_random_100000(b: &mut Bencher) { - bench_vec_dedup_random(b, 100000); + +fn dedup_gen_wide(x: u32) -> [u64; 4] { + [x as u64, (x as u64) << 8, (x as u64) << 16, (x as u64) << 24] } -#[bench] -fn bench_dedup_none_100000(b: &mut Bencher) { - bench_vec_dedup_none(b, 100000); +macro_rules! dedup_typed { + ($($name:ident: $gen:expr, $dist:ident, $sz:literal;)*) => { + $( + #[bench] + fn $name(b: &mut Bencher) { + bench_vec_dedup(b, DedupDist::$dist, $sz, $gen); + } + )* + }; } -#[bench] -fn bench_dedup_all_100000(b: &mut Bencher) { - bench_vec_dedup_all(b, 100000); +dedup_typed! { + bench_dedup_random_100: |x| x, Random, 100; + bench_dedup_none_100: |x| x, None, 100; + bench_dedup_all_100: |x| x, All, 100; + bench_dedup_random_1000: |x| x, Random, 1000; + bench_dedup_none_1000: |x| x, None, 1000; + bench_dedup_all_1000: |x| x, All, 1000; + bench_dedup_random_10000: |x| x, Random, 10000; + bench_dedup_none_10000: |x| x, None, 10000; + bench_dedup_all_10000: |x| x, All, 10000; + bench_dedup_random_100000: |x| x, Random, 100000; + bench_dedup_none_100000: |x| x, None, 100000; + bench_dedup_all_100000: |x| x, All, 100000; + bench_dedup_string_short_random_100: dedup_gen_string::<10>, Random, 100; + bench_dedup_string_short_none_100: dedup_gen_string::<10>, None, 100; + bench_dedup_string_short_all_100: dedup_gen_string::<10>, All, 100; + bench_dedup_string_short_random_1000: dedup_gen_string::<10>, Random, 1000; + bench_dedup_string_short_none_1000: dedup_gen_string::<10>, None, 1000; + bench_dedup_string_short_all_1000: dedup_gen_string::<10>, All, 1000; + bench_dedup_string_short_random_10000: dedup_gen_string::<10>, Random, 10000; + bench_dedup_string_short_none_10000: dedup_gen_string::<10>, None, 10000; + bench_dedup_string_short_all_10000: dedup_gen_string::<10>, All, 10000; + bench_dedup_string_medium_random_100: dedup_gen_string::<64>, Random, 100; + bench_dedup_string_medium_none_100: dedup_gen_string::<64>, None, 100; + bench_dedup_string_medium_all_100: dedup_gen_string::<64>, All, 100; + bench_dedup_string_medium_random_1000: dedup_gen_string::<64>, Random, 1000; + bench_dedup_string_medium_none_1000: dedup_gen_string::<64>, None, 1000; + bench_dedup_string_medium_all_1000: dedup_gen_string::<64>, All, 1000; + bench_dedup_string_medium_random_10000: dedup_gen_string::<64>, Random, 10000; + bench_dedup_string_medium_none_10000: dedup_gen_string::<64>, None, 10000; + bench_dedup_string_medium_all_10000: dedup_gen_string::<64>, All, 10000; + bench_dedup_string_long_random_100: dedup_gen_string::<1024>, Random, 100; + bench_dedup_string_long_none_100: dedup_gen_string::<1024>, None, 100; + bench_dedup_string_long_all_100: dedup_gen_string::<1024>, All, 100; + bench_dedup_string_long_random_1000: dedup_gen_string::<1024>, Random, 1000; + bench_dedup_string_long_none_1000: dedup_gen_string::<1024>, None, 1000; + bench_dedup_string_long_all_1000: dedup_gen_string::<1024>, All, 1000; + bench_dedup_string_long_random_10000: dedup_gen_string::<1024>, Random, 10000; + bench_dedup_string_long_none_10000: dedup_gen_string::<1024>, None, 10000; + bench_dedup_string_long_all_10000: dedup_gen_string::<1024>, All, 10000; + bench_dedup_u8_random_1000: |x| x as u8, Random, 1000; + bench_dedup_u8_none_1000: |x| x as u8, None, 1000; + bench_dedup_u8_all_1000: |x| x as u8, All, 1000; + bench_dedup_u8_random_10000: |x| x as u8, Random, 10000; + bench_dedup_u8_none_10000: |x| x as u8, None, 10000; + bench_dedup_u8_all_10000: |x| x as u8, All, 10000; + bench_dedup_u16_random_1000: |x| x as u16, Random, 1000; + bench_dedup_u16_none_1000: |x| x as u16, None, 1000; + bench_dedup_u16_all_1000: |x| x as u16, All, 1000; + bench_dedup_u16_random_10000: |x| x as u16, Random, 10000; + bench_dedup_u16_none_10000: |x| x as u16, None, 10000; + bench_dedup_u16_all_10000: |x| x as u16, All, 10000; + bench_dedup_u64_random_1000: |x| x as u64, Random, 1000; + bench_dedup_u64_none_1000: |x| x as u64, None, 1000; + bench_dedup_u64_all_1000: |x| x as u64, All, 1000; + bench_dedup_u64_random_10000: |x| x as u64, Random, 10000; + bench_dedup_u64_none_10000: |x| x as u64, None, 10000; + bench_dedup_u64_all_10000: |x| x as u64, All, 10000; + bench_dedup_wide_random_1000: dedup_gen_wide, Random, 1000; + bench_dedup_wide_none_1000: dedup_gen_wide, None, 1000; + bench_dedup_wide_all_1000: dedup_gen_wide, All, 1000; + bench_dedup_wide_random_10000: dedup_gen_wide, Random, 10000; + bench_dedup_wide_none_10000: dedup_gen_wide, None, 10000; + bench_dedup_wide_all_10000: dedup_gen_wide, All, 10000; } #[bench] diff --git a/library/alloctests/tests/vec.rs b/library/alloctests/tests/vec.rs index 787452df7f680..cfb6a6bd03824 100644 --- a/library/alloctests/tests/vec.rs +++ b/library/alloctests/tests/vec.rs @@ -2534,6 +2534,94 @@ fn test_vec_dedup_panicking() { } } +#[test] +fn test_vec_dedup_long() { + fn check(template: &[T]) { + let mut expected = template.to_vec(); + let (kept, _) = expected.partition_dedup(); + let kept_len = kept.len(); + expected.truncate(kept_len); + + let mut vec = template.to_vec(); + vec.dedup(); + assert_eq!(vec, expected, "input: {template:?}"); + } + + macro_rules! check_type { + ($ty:ty) => { + for len in [0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65] { + let distinct: Vec<$ty> = (0..len).map(|x| x as $ty).collect(); + check(&distinct); + check(&vec![7 as $ty; len]); + + for dup_at in 1..len { + let mut v = distinct.clone(); + v[dup_at] = v[dup_at - 1]; + check(&v); + + if dup_at + 1 < len { + let mut w = v.clone(); + w[len - 1] = w[len - 2]; + check(&w); + } + } + } + }; + } + + check_type!(u8); + check_type!(u16); + check_type!(u32); + check_type!(u64); +} + +#[test] +fn test_vec_dedup_long_boxed() { + for len in [9, 10, 16, 17, 24, 25, 33] { + for dup_at in 1..len { + let mut v: Vec> = (0..len as u32).map(Box::new).collect(); + v[dup_at] = Box::new(*v[dup_at - 1]); + let expected: Vec = (0..len as u32).filter(|&x| x != dup_at as u32).collect(); + v.dedup(); + let got: Vec = v.iter().map(|b| **b).collect(); + assert_eq!(got, expected, "len={len} dup_at={dup_at}"); + } + } +} + +#[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] +fn test_vec_dedup_eq_panics_for_simd_sized_type() { + static EQ_COUNT: AtomicU32 = AtomicU32::new(0); + static DROP_COUNT: AtomicU32 = AtomicU32::new(0); + + #[derive(Debug)] + #[repr(transparent)] + struct Panic(u32); + + impl PartialEq for Panic { + fn eq(&self, other: &Self) -> bool { + if EQ_COUNT.fetch_add(1, Ordering::SeqCst) == 2 { + panic!("comparison panic"); + } + self.0 == other.0 + } + } + + impl Drop for Panic { + fn drop(&mut self) { + DROP_COUNT.fetch_add(1, Ordering::SeqCst); + } + } + + EQ_COUNT.store(0, Ordering::SeqCst); + DROP_COUNT.store(0, Ordering::SeqCst); + let mut vec: Vec<_> = [0, 0, 1, 2, 3, 4, 5, 6, 7].into_iter().map(Panic).collect(); + assert!(catch_unwind(AssertUnwindSafe(|| vec.dedup())).is_err()); + drop(vec); + assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 9); +} + // Regression test for issue #82533 #[test] #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] From 3fe740638c59366c701fb4b2fefbbdc966e283cc Mon Sep 17 00:00:00 2001 From: Valentyn Kit Date: Sun, 4 Oct 2026 18:24:27 +0300 Subject: [PATCH 06/11] std: fix failing `test_rename_noreplace_directory` `test_rename_noreplace_directory()` fails on kernels before Linux 3.15, because they lack `renameat2`, so std implementation falls back to `link`/`unlink`, which couldn't move directories, so expectations of the test were wrong. Test now accepts `PermissionDenied` on Unix, in fallback case. --- library/std/src/fs/tests.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 98d8708b5ad86..0f8d876e3c00a 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -2700,8 +2700,6 @@ fn test_rename_noreplace_directory_to_empty_directory() { #[test] #[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple", windows))] fn test_rename_noreplace_directory() { - // The `link`/`unlink` fallback cannot move directories, so only platforms - // with a native no-replace rename are tested. let tmpdir = tmpdir(); let source_path = tmpdir.join("source_directory"); let target_path = tmpdir.join("target_directory"); @@ -2709,9 +2707,19 @@ fn test_rename_noreplace_directory() { fs::create_dir(&source_path).unwrap(); fs::write(source_path.join("file.txt"), b"hello world").unwrap(); - fs::rename_noreplace(&source_path, &target_path).unwrap(); - assert!(!source_path.exists()); - assert_eq!(fs::read(target_path.join("file.txt")).unwrap(), b"hello world"); + match fs::rename_noreplace(&source_path, &target_path) { + Ok(()) => { + assert!(!source_path.exists()); + assert_eq!(fs::read(target_path.join("file.txt")).unwrap(), b"hello world"); + } + // Without a native no-replace rename (Linux before 3.15), Unix + // platforms fall back to `link`/`unlink`, and it rejects directories. + Err(err) if cfg!(unix) && err.kind() == ErrorKind::PermissionDenied => { + assert!(source_path.exists()); + assert!(!target_path.exists()); + } + Err(err) => panic!("unexpected error: {err:?}"), + } } #[test] From 926a9d0873b5b2a1412a7eb224274ceed90674cc Mon Sep 17 00:00:00 2001 From: ywxt Date: Thu, 8 Oct 2026 11:49:44 +0800 Subject: [PATCH 07/11] directly drop the tree instead of traversing every element when BTreeMap dropping --- library/alloc/src/collections/btree/map.rs | 15 +-- .../alloc/src/collections/btree/map/tests.rs | 108 ++++++++++++++++++ .../alloc/src/collections/btree/navigate.rs | 67 +++++++++++ library/alloc/src/collections/btree/node.rs | 39 +++++++ 4 files changed, 220 insertions(+), 9 deletions(-) diff --git a/library/alloc/src/collections/btree/map.rs b/library/alloc/src/collections/btree/map.rs index 109beed7de947..89e20e03afe83 100644 --- a/library/alloc/src/collections/btree/map.rs +++ b/library/alloc/src/collections/btree/map.rs @@ -205,16 +205,13 @@ pub struct BTreeMap< #[stable(feature = "btree_drop", since = "1.7.0")] unsafe impl<#[may_dangle] K, #[may_dangle] V, A: AllocatorClone> Drop for BTreeMap { fn drop(&mut self) { - // Skip `into_iter` for an empty map: `dying_next` is too costly to inline, so the - // empty drop isn't optimised away (see #161375). - if self.root.is_some() { - // SAFETY: `self` is not used after this and none of its fields are dropped again: - // `alloc` is `ManuallyDrop` and `root` has no drop glue. - drop(unsafe { ptr::read(self) }.into_iter()) + if let Some(root) = self.root.take() { + // SAFETY: The tree won't be used after this point. + // The allocator has not been moved out and will be moved into `drop_tree`. + unsafe { root.into_dying().drop_tree(ManuallyDrop::take(&mut self.alloc)) }; } else { - // SAFETY: With no root there are no nodes to free, so only the allocator needs - // dropping. `self` is not used after this, and `alloc` is dropped only here. - unsafe { ManuallyDrop::drop(&mut self.alloc) } + // SAFETY: The allocator has not been moved out and won't be used after this point. + unsafe { ManuallyDrop::drop(&mut self.alloc) }; } } } diff --git a/library/alloc/src/collections/btree/map/tests.rs b/library/alloc/src/collections/btree/map/tests.rs index a920254872e19..c4c9148e0abfb 100644 --- a/library/alloc/src/collections/btree/map/tests.rs +++ b/library/alloc/src/collections/btree/map/tests.rs @@ -2542,6 +2542,114 @@ fn test_into_iter_drop_leak_height_1() { } } +#[test] +fn test_drop_map_elements_multi_level() { + for size in [1, MIN_INSERTS_HEIGHT_1, MIN_INSERTS_HEIGHT_2] { + let keys = Vec::from_iter((0..size).map(CrashTestDummy::new)); + let vals = Vec::from_iter((0..size).map(CrashTestDummy::new)); + let map = { + let mut map = BTreeMap::new(); + + (0..size).map(|i| (keys[i].spawn(Panic::Never), vals[i].spawn(Panic::Never))).for_each( + |(k, v)| { + map.insert(k, v); + }, + ); + map + }; + + drop(map); + + for i in 0..size { + assert_eq!(keys[i].dropped(), 1); + assert_eq!(vals[i].dropped(), 1); + } + } +} + +#[test] +fn test_drop_empty_map_allocator() { + use core::alloc::{AllocError, Allocator, AllocatorClone, Layout}; + use core::cell::Cell; + use core::ptr::NonNull; + + struct CountedAllocator<'a>(&'a Cell); + + impl Clone for CountedAllocator<'_> { + fn clone(&self) -> Self { + Self(self.0) + } + } + + impl Drop for CountedAllocator<'_> { + fn drop(&mut self) { + self.0.set(self.0.get() + 1); + } + } + + unsafe impl Allocator for CountedAllocator<'_> { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + Global.allocate(layout) + } + + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + unsafe { Global.deallocate(ptr, layout) } + } + } + + unsafe impl AllocatorClone for CountedAllocator<'_> {} + + let dropped = Cell::new(0); + let map = BTreeMap::::new_in(CountedAllocator(&dropped)); + assert!(map.root.is_none()); + + drop(map); + + assert_eq!(dropped.get(), 1); +} + +#[test] +#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")] +fn test_drop_map_elements_multi_level_panic() { + for size in [1, MIN_INSERTS_HEIGHT_1, MIN_INSERTS_HEIGHT_2] { + for panic_point in [0, size / 2, size - 1] { + for panic_in_key in [true, false] { + let keys = Vec::from_iter((0..size).map(CrashTestDummy::new)); + let vals = Vec::from_iter((0..size).map(CrashTestDummy::new)); + let map = { + let mut map = BTreeMap::new(); + + (0..size) + .map(|i| { + let key_panic = if panic_in_key && i == panic_point { + Panic::InDrop + } else { + Panic::Never + }; + let val_panic = if !panic_in_key && i == panic_point { + Panic::InDrop + } else { + Panic::Never + }; + (keys[i].spawn(key_panic), vals[i].spawn(val_panic)) + }) + .for_each(|(k, v)| { + map.insert(k, v); + }); + map + }; + + catch_unwind(move || drop(map)).unwrap_err(); + + for i in 0..size { + assert_eq!(keys[i].dropped(), 1); + assert_eq!(vals[i].dropped(), 1); + } + } + } + } +} + #[test] fn test_into_keys() { let map = BTreeMap::from([(1, 'a'), (2, 'b'), (3, 'c')]); diff --git a/library/alloc/src/collections/btree/navigate.rs b/library/alloc/src/collections/btree/navigate.rs index 28054cdeb5dd6..1f827d198468c 100644 --- a/library/alloc/src/collections/btree/navigate.rs +++ b/library/alloc/src/collections/btree/navigate.rs @@ -807,3 +807,70 @@ impl NodeRef NodeRef { + /// # Safety + /// - `self` must be the root. + /// - The tree shouldn't be accessed anymore. + pub(super) unsafe fn drop_tree(self, alloc: A) { + struct DropGuard { + node: Option>, + alloc: A, + } + + impl Drop for DropGuard { + fn drop(&mut self) { + if let Some(cur) = self.node.take() { + let mut next = + // SAFETY: When unwinding reached this guard, all kvs in cur + // have already been dropped in `drop_kvs`, while the node itself has not + // yet been deallocated. + unsafe { deallocate_and_next_postorder_node(cur, self.alloc.clone()) }; + + while let Some(mut node) = next { + // SAFETY: Every node only calls the function once + unsafe { node.drop_kvs() }; + next = + // SAFETY: `drop_kvs()` above only dropped kvs and the node is still accessible, + // and also each node is deallocated exactly once. + unsafe { deallocate_and_next_postorder_node(node, self.alloc.clone()) } + } + } + } + } + /// Deallocates `node` and returns the next node in postorder traversal + /// + /// # Safety + /// + /// The node must be initialized, all kvs in node must have already + /// been dropped or moved out and its children must have already been deallocated. + /// Also the node can't be touched anymore. + unsafe fn deallocate_and_next_postorder_node( + node: NodeRef, + alloc: A, + ) -> Option> { + // SAFETY: guaranteed by the caller + let parent = unsafe { node.deallocate_and_ascend(alloc) }?; + + Some(match parent.right_kv() { + Ok(kv) => kv.right_edge().descend().first_leaf_edge().into_node().forget_type(), + Err(last_edge) => last_edge.into_node().forget_type(), + }) + } + + let node = self.first_leaf_edge().into_node().forget_type(); + let mut guard = DropGuard { node: Some(node), alloc }; + loop { + // SAFETY: all kvs of each node are dropped once + unsafe { guard.node.as_mut().unwrap().drop_kvs() }; + // SAFETY: Nodes are deallocated in postorder, and each node is deallocated exactly once. + if let Some(next) = unsafe { + deallocate_and_next_postorder_node(guard.node.take().unwrap(), guard.alloc.clone()) + } { + guard.node = Some(next); + } else { + return; + } + } + } +} diff --git a/library/alloc/src/collections/btree/node.rs b/library/alloc/src/collections/btree/node.rs index 47c3fb1d79c9d..2144efc5d7d4d 100644 --- a/library/alloc/src/collections/btree/node.rs +++ b/library/alloc/src/collections/btree/node.rs @@ -429,6 +429,45 @@ impl NodeRef { } ret } + /// Drops all kvs in this node without deallocating the node itself + /// + /// # Safety + /// + /// - kvs in the node must be initialized and must not be accessed after this call. + /// - No kvs in the node can be accessed through any other path while this function is running. + pub(super) unsafe fn drop_kvs(&mut self) { + struct DropGuard<'a, K, V> { + node: &'a NodeRef, + idx: usize, + } + impl<'a, K, V> Drop for DropGuard<'a, K, V> { + fn drop(&mut self) { + let len = self.node.len(); + for i in self.idx..len { + // SAFETY: i < len and the kv has been initialized and + // it won't use the i-th kv more than once. + unsafe { + let kv = Handle::new_kv(ptr::read(self.node), i); + kv.drop_key_val(); + } + } + } + } + let len = self.len(); + for idx in 0..len { + // If `kv.drop_key_val()` panics, the guard would drop + // the rest and skip the panicked kv. + let guard = DropGuard { node: self, idx: idx + 1 }; + // SAFETY: idx < len and the kv has been initialized and + // it won't touch the idx-th more than once + unsafe { + let kv = Handle::new_kv(ptr::read(self), idx); + kv.drop_key_val(); + } + + core::mem::forget(guard); + } + } } impl<'a, K, V, Type> NodeRef, K, V, Type> { From a6bcbeb36e2a2e87a815a829f5e87a01be9aa667 Mon Sep 17 00:00:00 2001 From: Seth Junot Date: Wed, 5 Aug 2026 18:08:34 -0700 Subject: [PATCH 08/11] bootstrap: always build llvm's dylib when linking to it The bootstrap example says that LLVM's dynamic library will be built when llvm.link-shared is true. It actually only set the flag that enables linking against the library (LLVM_LINK_LLVM_DYLIB). It should also set LLVM_BUILD_LLVM_DYLIB. On clean builds, LLVM's configuration helpfully sets the BUILD flag to true if the LINK flag is true. If a dev toggles link-shared from false to true after an initial build, LINK will be "ON" (given by bootstrap) and BUILD will be "OFF" (from the cmake cache). The library to link will not be built, causing the build to fail at link time. With this change, the library is always built when it's to be linked. Conversely, it is not built if it is not to be linked. --- src/bootstrap/src/core/build_steps/llvm.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index 4ac0f6ea85679..6a7760eba693a 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -557,11 +557,11 @@ impl CommandLineStep for Llvm { // which saves both memory during parallel links and overall disk space // for the tools. We don't do this on every platform as it doesn't work // equally well everywhere. - if link_shared { - cfg.define("LLVM_LINK_LLVM_DYLIB", "ON"); - // Keep the pre-LLVM23 behavior for now. - cfg.define("LLVM_VERSIONED_DYLIB_NAME_ON_DARWIN", "OFF"); - } + let llvm_dylib = if link_shared { "ON" } else { "OFF" }; + cfg.define("LLVM_LINK_LLVM_DYLIB", llvm_dylib); + cfg.define("LLVM_BUILD_LLVM_DYLIB", llvm_dylib); + // Keep the pre-LLVM23 behavior for now. + cfg.define("LLVM_VERSIONED_DYLIB_NAME_ON_DARWIN", "OFF"); if (target.starts_with("csky") || target.starts_with("riscv") From 32fd83f5e232b988662367347c8ed46c706f787d Mon Sep 17 00:00:00 2001 From: Max Dexheimer Date: Sun, 11 Oct 2026 10:13:33 +0200 Subject: [PATCH 09/11] Nuke Unique --- compiler/rustc_codegen_ssa/src/mir/retag.rs | 2 +- library/alloc/src/lib.rs | 1 - library/core/src/panic/unwind_safe.rs | 4 +- library/core/src/ptr/mod.rs | 4 - library/core/src/ptr/non_null.rs | 10 - library/core/src/ptr/unique.rs | 214 ------------------ src/tools/tidy/src/issues.txt | 1 - tests/debuginfo/marker-types.rs | 14 +- tests/ui/consts/const-ptr-unique-rpass.rs | 16 -- tests/ui/consts/const-ptr-unique.rs | 10 - tests/ui/consts/const-ptr-unique.stderr | 14 -- .../feature-gates/issue-49983-see-issue-0.rs | 6 - .../issue-49983-see-issue-0.stderr | 12 - tests/ui/lint/improper-ctypes/lint-enum.rs | 4 - .../ui/lint/improper-ctypes/lint-enum.stderr | 48 ++-- .../ui/rfcs/rfc-3391-result-ffi-guarantees.rs | 5 - 16 files changed, 28 insertions(+), 337 deletions(-) delete mode 100644 library/core/src/ptr/unique.rs delete mode 100644 tests/ui/consts/const-ptr-unique-rpass.rs delete mode 100644 tests/ui/consts/const-ptr-unique.rs delete mode 100644 tests/ui/consts/const-ptr-unique.stderr delete mode 100644 tests/ui/feature-gates/issue-49983-see-issue-0.rs delete mode 100644 tests/ui/feature-gates/issue-49983-see-issue-0.stderr diff --git a/compiler/rustc_codegen_ssa/src/mir/retag.rs b/compiler/rustc_codegen_ssa/src/mir/retag.rs index 6a3316fa93992..483df05e4d14f 100644 --- a/compiler/rustc_codegen_ssa/src/mir/retag.rs +++ b/compiler/rustc_codegen_ssa/src/mir/retag.rs @@ -151,7 +151,7 @@ impl<'a, 'tcx, V> RetagPlan { let boxed_ty = layout.ty.expect_boxed_ty(); let boxed_layout = bx.layout_of(boxed_ty); if let Some(mut plan) = Self::emit_retag(bx, boxed_layout, None, is_fn_entry) { - // `Unique` + // `BoxRaw` let unique = layout.field(bx, 0); assert_eq!(unique.fields.count(), 2); plan = plan.for_field(FieldIdx::ZERO); diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs index 62d62db771e52..f95cb5c58eaeb 100644 --- a/library/alloc/src/lib.rs +++ b/library/alloc/src/lib.rs @@ -157,7 +157,6 @@ #![feature(pin_coerce_unsized_trait)] #![feature(ptr_alignment_type)] #![feature(ptr_cast_slice)] -#![feature(ptr_internals)] #![feature(ptr_metadata)] #![feature(random)] #![feature(raw_os_error_ty)] diff --git a/library/core/src/panic/unwind_safe.rs b/library/core/src/panic/unwind_safe.rs index 60c833db33e15..79141ec4370d7 100644 --- a/library/core/src/panic/unwind_safe.rs +++ b/library/core/src/panic/unwind_safe.rs @@ -5,7 +5,7 @@ use crate::future::Future; use crate::marker::PointeeSized; use crate::ops::{Deref, DerefMut}; use crate::pin::Pin; -use crate::ptr::{NonNull, Unique}; +use crate::ptr::NonNull; use crate::task::{Context, Poll}; /// A marker trait which represents "panic safe" types in Rust. @@ -186,8 +186,6 @@ impl UnwindSafe for &T {} impl UnwindSafe for *const T {} #[stable(feature = "catch_unwind", since = "1.9.0")] impl UnwindSafe for *mut T {} -#[unstable(feature = "ptr_internals", issue = "none")] -impl UnwindSafe for Unique {} #[stable(feature = "nonnull", since = "1.25.0")] impl UnwindSafe for NonNull {} #[stable(feature = "catch_unwind", since = "1.9.0")] diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs index 9019e3a486921..27951a65f6c84 100644 --- a/library/core/src/ptr/mod.rs +++ b/library/core/src/ptr/mod.rs @@ -447,10 +447,6 @@ mod non_null; #[stable(feature = "nonnull", since = "1.25.0")] pub use non_null::NonNull; -mod unique; -#[unstable(feature = "ptr_internals", issue = "none")] -pub use unique::Unique; - mod const_ptr; mod mut_ptr; diff --git a/library/core/src/ptr/non_null.rs b/library/core/src/ptr/non_null.rs index ae6d194ff6929..dc67d94d6e08e 100644 --- a/library/core/src/ptr/non_null.rs +++ b/library/core/src/ptr/non_null.rs @@ -4,7 +4,6 @@ use crate::marker::{Destruct, PointeeSized, Unsize}; use crate::mem::{MaybeUninit, SizedTypeProperties, transmute}; use crate::num::NonZero; use crate::ops::{CoerceUnsized, DispatchFromDyn}; -use crate::ptr::Unique; use crate::slice::{self, SliceIndex}; use crate::ub_checks::assert_unsafe_precondition; use crate::{fmt, hash, intrinsics, mem, ptr}; @@ -1691,15 +1690,6 @@ impl hash::Hash for NonNull { } } -#[unstable(feature = "ptr_internals", issue = "none")] -#[rustc_const_unstable(feature = "const_convert", issue = "143773")] -const impl From> for NonNull { - #[inline] - fn from(unique: Unique) -> Self { - unique.as_non_null_ptr() - } -} - #[stable(feature = "nonnull", since = "1.25.0")] #[rustc_const_unstable(feature = "const_convert", issue = "143773")] const impl From<&mut T> for NonNull { diff --git a/library/core/src/ptr/unique.rs b/library/core/src/ptr/unique.rs deleted file mode 100644 index 5e3fe666ac9ba..0000000000000 --- a/library/core/src/ptr/unique.rs +++ /dev/null @@ -1,214 +0,0 @@ -use crate::clone::TrivialClone; -use crate::fmt; -use crate::marker::{PhantomData, PointeeSized, Unsize}; -use crate::ops::{CoerceUnsized, DispatchFromDyn}; -use crate::ptr::NonNull; - -/// A wrapper around a raw non-null `*mut T` that indicates that the possessor -/// of this wrapper owns the referent. Useful for building abstractions like -/// `Box`, `Vec`, `String`, and `HashMap`. -/// -/// Unlike `*mut T`, `Unique` behaves "as if" it were an instance of `T`. -/// It implements `Send`/`Sync` if `T` is `Send`/`Sync`. It also implies -/// the kind of strong aliasing guarantees an instance of `T` can expect: -/// the referent of the pointer should not be modified without a unique path to -/// its owning Unique. -/// -/// If you're uncertain of whether it's correct to use `Unique` for your purposes, -/// consider using `NonNull`, which has weaker semantics. -/// -/// Unlike `*mut T`, the pointer must always be non-null, even if the pointer -/// is never dereferenced. This is so that enums may use this forbidden value -/// as a discriminant -- `Option>` has the same size as `Unique`. -/// However the pointer may still dangle if it isn't dereferenced. -/// -/// Unlike `*mut T`, `Unique` is covariant over `T`. This should always be correct -/// for any type which upholds Unique's aliasing requirements. -#[unstable( - feature = "ptr_internals", - issue = "none", - reason = "use `NonNull` instead and consider `PhantomData` \ - (if you also use `#[may_dangle]`), `Send`, and/or `Sync`" -)] -#[doc(hidden)] -#[repr(transparent)] -pub struct Unique { - pointer: NonNull, - // NOTE: this marker has no consequences for variance, but is necessary - // for dropck to understand that we logically own a `T`. - // - // For details, see: - // https://rust-lang.github.io/rfcs/0769-sound-generic-drop.html#phantom-data - _marker: PhantomData, -} - -/// `Unique` pointers are `Send` if `T` is `Send` because the data they -/// reference is unaliased. Note that this aliasing invariant is -/// unenforced by the type system; the abstraction using the -/// `Unique` must enforce it. -#[unstable(feature = "ptr_internals", issue = "none")] -unsafe impl Send for Unique {} - -/// `Unique` pointers are `Sync` if `T` is `Sync` because the data they -/// reference is unaliased. Note that this aliasing invariant is -/// unenforced by the type system; the abstraction using the -/// `Unique` must enforce it. -#[unstable(feature = "ptr_internals", issue = "none")] -unsafe impl Sync for Unique {} - -#[unstable(feature = "ptr_internals", issue = "none")] -impl Unique { - /// Creates a new `Unique` that is dangling, but well-aligned. - /// - /// This is useful for initializing types which lazily allocate, like - /// `Vec::new` does. - /// - /// Note that the address of the returned pointer may potentially - /// be that of a valid pointer, which means this must not be used - /// as a "not yet initialized" sentinel value. - /// Types that lazily allocate must track initialization by some other means. - #[must_use] - #[inline] - pub const fn dangling() -> Self { - // FIXME(const-hack) replace with `From` - Unique { pointer: NonNull::dangling(), _marker: PhantomData } - } -} - -#[unstable(feature = "ptr_internals", issue = "none")] -impl Unique { - /// Creates a new `Unique`. - /// - /// # Safety - /// - /// `ptr` must be non-null. - #[inline] - pub const unsafe fn new_unchecked(ptr: *mut T) -> Self { - // SAFETY: the caller must guarantee that `ptr` is non-null. - unsafe { Unique { pointer: NonNull::new_unchecked(ptr), _marker: PhantomData } } - } - - /// Creates a new `Unique` if `ptr` is non-null. - #[inline] - pub const fn new(ptr: *mut T) -> Option { - if let Some(pointer) = NonNull::new(ptr) { - Some(Unique { pointer, _marker: PhantomData }) - } else { - None - } - } - - /// Create a new `Unique` from a `NonNull` in const context. - #[inline] - pub const fn from_non_null(pointer: NonNull) -> Self { - Unique { pointer, _marker: PhantomData } - } - - /// Acquires the underlying `*mut` pointer. - #[must_use = "`self` will be dropped if the result is not used"] - #[inline] - pub const fn as_ptr(self) -> *mut T { - self.pointer.as_ptr() - } - - /// Acquires the underlying `*mut` pointer. - #[must_use = "`self` will be dropped if the result is not used"] - #[inline] - pub const fn as_non_null_ptr(self) -> NonNull { - self.pointer - } - - /// Dereferences the content. - /// - /// The resulting lifetime is bound to self so this behaves "as if" - /// it were actually an instance of T that is getting borrowed. If a longer - /// (unbound) lifetime is needed, use `&*my_ptr.as_ptr()`. - #[must_use] - #[inline] - pub const unsafe fn as_ref(&self) -> &T { - // SAFETY: the caller must guarantee that `self` meets all the - // requirements for a reference. - unsafe { self.pointer.as_ref() } - } - - /// Mutably dereferences the content. - /// - /// The resulting lifetime is bound to self so this behaves "as if" - /// it were actually an instance of T that is getting borrowed. If a longer - /// (unbound) lifetime is needed, use `&mut *my_ptr.as_ptr()`. - #[must_use] - #[inline] - pub const unsafe fn as_mut(&mut self) -> &mut T { - // SAFETY: the caller must guarantee that `self` meets all the - // requirements for a mutable reference. - unsafe { self.pointer.as_mut() } - } - - /// Casts to a pointer of another type. - #[must_use = "`self` will be dropped if the result is not used"] - #[inline] - pub const fn cast(self) -> Unique { - // FIXME(const-hack): replace with `From` - // SAFETY: is `NonNull` - Unique { pointer: self.pointer.cast(), _marker: PhantomData } - } -} - -#[unstable(feature = "ptr_internals", issue = "none")] -impl Clone for Unique { - #[inline] - fn clone(&self) -> Self { - *self - } -} - -#[unstable(feature = "ptr_internals", issue = "none")] -impl Copy for Unique {} - -#[doc(hidden)] -#[unstable(feature = "trivial_clone", issue = "none")] -unsafe impl TrivialClone for Unique {} - -#[unstable(feature = "ptr_internals", issue = "none")] -impl CoerceUnsized> for Unique where T: Unsize {} - -#[unstable(feature = "ptr_internals", issue = "none")] -impl DispatchFromDyn> for Unique where T: Unsize {} - -#[unstable(feature = "ptr_internals", issue = "none")] -impl fmt::Debug for Unique { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Pointer::fmt(&self.as_ptr(), f) - } -} - -#[unstable(feature = "ptr_internals", issue = "none")] -impl fmt::Pointer for Unique { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Pointer::fmt(&self.as_ptr(), f) - } -} - -#[unstable(feature = "ptr_internals", issue = "none")] -#[rustc_const_unstable(feature = "const_convert", issue = "143773")] -const impl From<&mut T> for Unique { - /// Converts a `&mut T` to a `Unique`. - /// - /// This conversion is infallible since references cannot be null. - #[inline] - fn from(reference: &mut T) -> Self { - Self::from(NonNull::from(reference)) - } -} - -#[unstable(feature = "ptr_internals", issue = "none")] -#[rustc_const_unstable(feature = "const_convert", issue = "143773")] -const impl From> for Unique { - /// Converts a `NonNull` to a `Unique`. - /// - /// This conversion is infallible since `NonNull` cannot be null. - #[inline] - fn from(pointer: NonNull) -> Self { - Unique::from_non_null(pointer) - } -} diff --git a/src/tools/tidy/src/issues.txt b/src/tools/tidy/src/issues.txt index 9b878307afc96..2b19015b9c2b0 100644 --- a/src/tools/tidy/src/issues.txt +++ b/src/tools/tidy/src/issues.txt @@ -977,7 +977,6 @@ ui/feature-gates/issue-43106-gating-of-proc_macro_derive.rs ui/feature-gates/issue-43106-gating-of-stable.rs ui/feature-gates/issue-43106-gating-of-test.rs ui/feature-gates/issue-43106-gating-of-unstable.rs -ui/feature-gates/issue-49983-see-issue-0.rs ui/fmt/issue-103826.rs ui/fmt/issue-104142.rs ui/fmt/issue-23781.rs diff --git a/tests/debuginfo/marker-types.rs b/tests/debuginfo/marker-types.rs index 3c9cba608178c..8e250944a4212 100644 --- a/tests/debuginfo/marker-types.rs +++ b/tests/debuginfo/marker-types.rs @@ -21,17 +21,9 @@ //@ cdb-check: [capacity] : 0x4 [Type: unsigned [...]] //@ cdb-check: [chars] : "this" -//@ cdb-command: dx unique -//@ cdb-check:unique : Unique(0x[...]: (0x2a, 4321)) [Type: core::ptr::unique::Unique >] -//@ cdb-check: [] [Type: core::ptr::unique::Unique >] -//@ cdb-check: [0] : 0x2a [Type: unsigned __int64] -//@ cdb-check: [1] : 4321 [Type: int] - -#![feature(ptr_internals)] - use std::mem::ManuallyDrop; use std::pin::Pin; -use std::ptr::{NonNull, Unique}; +use std::ptr::NonNull; fn main() { let nonnull: NonNull<_> = (&12u32).into(); @@ -41,9 +33,7 @@ fn main() { let mut s = "this".to_string(); let pin = Pin::new(&mut s); - let unique: Unique<_> = (&mut (42u64, 4321i32)).into(); - zzz(); // #break } -fn zzz() { } +fn zzz() {} diff --git a/tests/ui/consts/const-ptr-unique-rpass.rs b/tests/ui/consts/const-ptr-unique-rpass.rs deleted file mode 100644 index db319b6ab9229..0000000000000 --- a/tests/ui/consts/const-ptr-unique-rpass.rs +++ /dev/null @@ -1,16 +0,0 @@ -//@ run-pass - -#![feature(ptr_internals, test)] - -extern crate test; -use test::black_box as b; // prevent promotion of the argument and const-propagation of the result - -use std::ptr::Unique; - - -const PTR: *mut u32 = Unique::dangling().as_ptr(); - -pub fn main() { - // Be super-extra paranoid and cast the fn items to fn pointers before blackboxing them. - assert_eq!(PTR, b:: _>(Unique::::dangling)().as_ptr()); -} diff --git a/tests/ui/consts/const-ptr-unique.rs b/tests/ui/consts/const-ptr-unique.rs deleted file mode 100644 index 252c5d1a9cda5..0000000000000 --- a/tests/ui/consts/const-ptr-unique.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![feature(ptr_internals)] - -use std::ptr::Unique; - -fn main() { - let mut i: u32 = 10; - let unique = Unique::new(&mut i).unwrap(); - let x: &'static *mut u32 = &(unique.as_ptr()); - //~^ ERROR temporary value dropped while borrowed -} diff --git a/tests/ui/consts/const-ptr-unique.stderr b/tests/ui/consts/const-ptr-unique.stderr deleted file mode 100644 index c3cc69830dfd8..0000000000000 --- a/tests/ui/consts/const-ptr-unique.stderr +++ /dev/null @@ -1,14 +0,0 @@ -error[E0716]: temporary value dropped while borrowed - --> $DIR/const-ptr-unique.rs:8:33 - | -LL | let x: &'static *mut u32 = &(unique.as_ptr()); - | ----------------- ^^^^^^^^^^^^^^^^^ creates a temporary value which is freed while still in use - | | - | type annotation requires that borrow lasts for `'static` -LL | -LL | } - | - temporary value is freed at the end of this statement - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0716`. diff --git a/tests/ui/feature-gates/issue-49983-see-issue-0.rs b/tests/ui/feature-gates/issue-49983-see-issue-0.rs deleted file mode 100644 index eeb80d014b2d5..0000000000000 --- a/tests/ui/feature-gates/issue-49983-see-issue-0.rs +++ /dev/null @@ -1,6 +0,0 @@ -extern crate core; - -// error should not say "(see issue #0)" -#[allow(unused_imports)] use core::ptr::Unique; //~ ERROR use of unstable library feature - -fn main() {} diff --git a/tests/ui/feature-gates/issue-49983-see-issue-0.stderr b/tests/ui/feature-gates/issue-49983-see-issue-0.stderr deleted file mode 100644 index 29a2845852e44..0000000000000 --- a/tests/ui/feature-gates/issue-49983-see-issue-0.stderr +++ /dev/null @@ -1,12 +0,0 @@ -error[E0658]: use of unstable library feature `ptr_internals`: use `NonNull` instead and consider `PhantomData` (if you also use `#[may_dangle]`), `Send`, and/or `Sync` - --> $DIR/issue-49983-see-issue-0.rs:4:30 - | -LL | #[allow(unused_imports)] use core::ptr::Unique; - | ^^^^^^^^^^^^^^^^^ - | - = help: add `#![feature(ptr_internals)]` to the crate attributes to enable - = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date - -error: aborting due to 1 previous error - -For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/lint/improper-ctypes/lint-enum.rs b/tests/ui/lint/improper-ctypes/lint-enum.rs index f900f998d06cb..cfc9842bd6e15 100644 --- a/tests/ui/lint/improper-ctypes/lint-enum.rs +++ b/tests/ui/lint/improper-ctypes/lint-enum.rs @@ -1,6 +1,5 @@ #![allow(dead_code)] #![deny(improper_ctypes)] -#![feature(ptr_internals)] #![feature(transparent_unions)] use std::num; @@ -90,7 +89,6 @@ extern "C" { fn option_ref(x: Option<&'static u8>); fn option_fn(x: Option); fn option_nonnull(x: Option>); - fn option_unique(x: Option>); fn option_nonzero_u8(x: Option>); fn option_nonzero_u16(x: Option>); fn option_nonzero_u32(x: Option>); @@ -113,7 +111,6 @@ extern "C" { fn result_ref_t(x: Result<&'static u8, ()>); fn result_fn_t(x: Result); fn result_nonnull_t(x: Result, ()>); - fn result_unique_t(x: Result, ()>); fn result_nonzero_u8_t(x: Result, ()>); fn result_nonzero_u16_t(x: Result, ()>); fn result_nonzero_u32_t(x: Result, ()>); @@ -149,7 +146,6 @@ extern "C" { fn result_ref_e(x: Result<(), &'static u8>); fn result_fn_e(x: Result<(), extern "C" fn()>); fn result_nonnull_e(x: Result<(), std::ptr::NonNull>); - fn result_unique_e(x: Result<(), std::ptr::Unique>); fn result_nonzero_u8_e(x: Result<(), num::NonZero>); fn result_nonzero_u16_e(x: Result<(), num::NonZero>); fn result_nonzero_u32_e(x: Result<(), num::NonZero>); diff --git a/tests/ui/lint/improper-ctypes/lint-enum.stderr b/tests/ui/lint/improper-ctypes/lint-enum.stderr index 35d1dcb87fd82..3a41f577c86d3 100644 --- a/tests/ui/lint/improper-ctypes/lint-enum.stderr +++ b/tests/ui/lint/improper-ctypes/lint-enum.stderr @@ -1,5 +1,5 @@ error: `extern` block uses type `U`, which is not FFI-safe - --> $DIR/lint-enum.rs:82:14 + --> $DIR/lint-enum.rs:81:14 | LL | fn uf(x: U); | ^ not FFI-safe @@ -7,7 +7,7 @@ LL | fn uf(x: U); = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint note: the type is defined here - --> $DIR/lint-enum.rs:9:1 + --> $DIR/lint-enum.rs:8:1 | LL | enum U { | ^^^^^^ @@ -18,7 +18,7 @@ LL | #![deny(improper_ctypes)] | ^^^^^^^^^^^^^^^ error: `extern` block uses type `B`, which is not FFI-safe - --> $DIR/lint-enum.rs:83:14 + --> $DIR/lint-enum.rs:82:14 | LL | fn bf(x: B); | ^ not FFI-safe @@ -26,13 +26,13 @@ LL | fn bf(x: B); = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint note: the type is defined here - --> $DIR/lint-enum.rs:12:1 + --> $DIR/lint-enum.rs:11:1 | LL | enum B { | ^^^^^^ error: `extern` block uses type `T`, which is not FFI-safe - --> $DIR/lint-enum.rs:84:14 + --> $DIR/lint-enum.rs:83:14 | LL | fn tf(x: T); | ^ not FFI-safe @@ -40,13 +40,13 @@ LL | fn tf(x: T); = help: consider adding a `#[repr(C)]`, `#[repr(transparent)]`, or integer `#[repr(...)]` attribute to this enum = note: enum has no representation hint note: the type is defined here - --> $DIR/lint-enum.rs:16:1 + --> $DIR/lint-enum.rs:15:1 | LL | enum T { | ^^^^^^ error: `extern` block uses type `Option>>`, which is not FFI-safe - --> $DIR/lint-enum.rs:108:36 + --> $DIR/lint-enum.rs:106:36 | LL | fn option_transparent_union(x: Option>>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -55,7 +55,7 @@ LL | fn option_transparent_union(x: Option = note: enum has no representation hint error: `extern` block uses type `Option>>`, which is not FFI-safe - --> $DIR/lint-enum.rs:110:28 + --> $DIR/lint-enum.rs:108:28 | LL | fn option_repr_rust(x: Option>>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -64,7 +64,7 @@ LL | fn option_repr_rust(x: Option>>); = note: enum has no representation hint error: `extern` block uses type `Option`, which is not FFI-safe - --> $DIR/lint-enum.rs:111:21 + --> $DIR/lint-enum.rs:109:21 | LL | fn option_u8(x: Option); | ^^^^^^^^^^ not FFI-safe @@ -73,7 +73,7 @@ LL | fn option_u8(x: Option); = note: enum has no representation hint error: `extern` block uses type `Result>, ()>`, which is not FFI-safe - --> $DIR/lint-enum.rs:131:38 + --> $DIR/lint-enum.rs:128:38 | LL | fn result_transparent_union_t(x: Result>, ()>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -82,7 +82,7 @@ LL | fn result_transparent_union_t(x: Result>, ()>`, which is not FFI-safe - --> $DIR/lint-enum.rs:133:30 + --> $DIR/lint-enum.rs:130:30 | LL | fn result_repr_rust_t(x: Result>, ()>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -91,7 +91,7 @@ LL | fn result_repr_rust_t(x: Result>, ()>); = note: enum has no representation hint error: `extern` block uses type `Result, U>`, which is not FFI-safe - --> $DIR/lint-enum.rs:137:51 + --> $DIR/lint-enum.rs:134:51 | LL | fn result_1zst_exhaustive_single_variant_t(x: Result, U>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -100,7 +100,7 @@ LL | fn result_1zst_exhaustive_single_variant_t(x: Result, = note: enum has no representation hint error: `extern` block uses type `Result, B>`, which is not FFI-safe - --> $DIR/lint-enum.rs:139:53 + --> $DIR/lint-enum.rs:136:53 | LL | fn result_1zst_exhaustive_multiple_variant_t(x: Result, B>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -109,7 +109,7 @@ LL | fn result_1zst_exhaustive_multiple_variant_t(x: Result = note: enum has no representation hint error: `extern` block uses type `Result, NonExhaustive>`, which is not FFI-safe - --> $DIR/lint-enum.rs:141:51 + --> $DIR/lint-enum.rs:138:51 | LL | fn result_1zst_non_exhaustive_no_variant_t(x: Result, NonExhaustive>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -118,7 +118,7 @@ LL | fn result_1zst_non_exhaustive_no_variant_t(x: Result, = note: enum has no representation hint error: `extern` block uses type `Result, Field>`, which is not FFI-safe - --> $DIR/lint-enum.rs:144:49 + --> $DIR/lint-enum.rs:141:49 | LL | fn result_1zst_exhaustive_single_field_t(x: Result, Field>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -127,7 +127,7 @@ LL | fn result_1zst_exhaustive_single_field_t(x: Result, Fi = note: enum has no representation hint error: `extern` block uses type `Result>, ()>`, which is not FFI-safe - --> $DIR/lint-enum.rs:146:30 + --> $DIR/lint-enum.rs:143:30 | LL | fn result_cascading_t(x: Result>, ()>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -136,7 +136,7 @@ LL | fn result_cascading_t(x: Result>, ()>); = note: enum has no representation hint error: `extern` block uses type `Result<(), TransparentUnion>>`, which is not FFI-safe - --> $DIR/lint-enum.rs:167:38 + --> $DIR/lint-enum.rs:163:38 | LL | fn result_transparent_union_e(x: Result<(), TransparentUnion>>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -145,7 +145,7 @@ LL | fn result_transparent_union_e(x: Result<(), TransparentUnion>>`, which is not FFI-safe - --> $DIR/lint-enum.rs:169:30 + --> $DIR/lint-enum.rs:165:30 | LL | fn result_repr_rust_e(x: Result<(), Rust>>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -154,7 +154,7 @@ LL | fn result_repr_rust_e(x: Result<(), Rust>>); = note: enum has no representation hint error: `extern` block uses type `Result>`, which is not FFI-safe - --> $DIR/lint-enum.rs:173:51 + --> $DIR/lint-enum.rs:169:51 | LL | fn result_1zst_exhaustive_single_variant_e(x: Result>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -163,7 +163,7 @@ LL | fn result_1zst_exhaustive_single_variant_e(x: Result>`, which is not FFI-safe - --> $DIR/lint-enum.rs:175:53 + --> $DIR/lint-enum.rs:171:53 | LL | fn result_1zst_exhaustive_multiple_variant_e(x: Result>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -172,7 +172,7 @@ LL | fn result_1zst_exhaustive_multiple_variant_e(x: Result>`, which is not FFI-safe - --> $DIR/lint-enum.rs:177:51 + --> $DIR/lint-enum.rs:173:51 | LL | fn result_1zst_non_exhaustive_no_variant_e(x: Result>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -181,7 +181,7 @@ LL | fn result_1zst_non_exhaustive_no_variant_e(x: Result>`, which is not FFI-safe - --> $DIR/lint-enum.rs:180:49 + --> $DIR/lint-enum.rs:176:49 | LL | fn result_1zst_exhaustive_single_field_e(x: Result>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -190,7 +190,7 @@ LL | fn result_1zst_exhaustive_single_field_e(x: Result>>`, which is not FFI-safe - --> $DIR/lint-enum.rs:182:30 + --> $DIR/lint-enum.rs:178:30 | LL | fn result_cascading_e(x: Result<(), Result<(), num::NonZero>>); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not FFI-safe @@ -199,7 +199,7 @@ LL | fn result_cascading_e(x: Result<(), Result<(), num::NonZero>>); = note: enum has no representation hint error: `extern` block uses type `Result<(), ()>`, which is not FFI-safe - --> $DIR/lint-enum.rs:184:27 + --> $DIR/lint-enum.rs:180:27 | LL | fn result_unit_t_e(x: Result<(), ()>); | ^^^^^^^^^^^^^^ not FFI-safe diff --git a/tests/ui/rfcs/rfc-3391-result-ffi-guarantees.rs b/tests/ui/rfcs/rfc-3391-result-ffi-guarantees.rs index 9b8092526b950..b01aa16825286 100644 --- a/tests/ui/rfcs/rfc-3391-result-ffi-guarantees.rs +++ b/tests/ui/rfcs/rfc-3391-result-ffi-guarantees.rs @@ -24,7 +24,6 @@ which also will affect its size if the enum has a non-zero size. #![allow(dead_code)] #![deny(improper_ctypes)] -#![feature(ptr_internals)] use std::num; @@ -44,7 +43,6 @@ extern "C" { fn result_ref_t(x: Result<&'static u8, ()>); fn result_fn_t(x: Result); fn result_nonnull_t(x: Result, ()>); - fn result_unique_t(x: Result, ()>); fn result_nonzero_u8_t(x: Result, ()>); fn result_nonzero_u16_t(x: Result, ()>); fn result_nonzero_u32_t(x: Result, ()>); @@ -64,7 +62,6 @@ extern "C" { fn result_ref_e(x: Result<(), &'static u8>); fn result_fn_e(x: Result<(), extern "C" fn()>); fn result_nonnull_e(x: Result<(), std::ptr::NonNull>); - fn result_unique_e(x: Result<(), std::ptr::Unique>); fn result_nonzero_u8_e(x: Result<(), num::NonZero>); fn result_nonzero_u16_e(x: Result<(), num::NonZero>); fn result_nonzero_u32_e(x: Result<(), num::NonZero>); @@ -92,7 +89,6 @@ extern "C" { fn either_ref_t(x: Either<&'static u8, ()>); fn either_fn_t(x: Either); fn either_nonnull_t(x: Either, ()>); - fn either_unique_t(x: Either, ()>); fn either_nonzero_u8_t(x: Either, ()>); fn either_nonzero_u16_t(x: Either, ()>); fn either_nonzero_u32_t(x: Either, ()>); @@ -112,7 +108,6 @@ extern "C" { fn either_ref_e(x: Either<(), &'static u8>); fn either_fn_e(x: Either<(), extern "C" fn()>); fn either_nonnull_e(x: Either<(), std::ptr::NonNull>); - fn either_unique_e(x: Either<(), std::ptr::Unique>); fn either_nonzero_u8_e(x: Either<(), num::NonZero>); fn either_nonzero_u16_e(x: Either<(), num::NonZero>); fn either_nonzero_u32_e(x: Either<(), num::NonZero>); From 5fd1cb666fd2c8849e362b5b4f5d6e311b589115 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 11 Oct 2026 12:44:15 +0200 Subject: [PATCH 10/11] std: add doctest to parent_id --- library/std/src/os/unix/process.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/library/std/src/os/unix/process.rs b/library/std/src/os/unix/process.rs index 9fa731de82691..036f2a5a3372c 100644 --- a/library/std/src/os/unix/process.rs +++ b/library/std/src/os/unix/process.rs @@ -747,6 +747,14 @@ impl From for process::ChildStderr { } /// Returns the OS-assigned process identifier associated with this process's parent. +/// +/// # Example +/// +/// ```rust +/// use std::os::unix::process; +/// let parent = process::parent_id(); +/// println!("The parent process has ID {}", parent); +/// ``` #[must_use] #[stable(feature = "unix_ppid", since = "1.27.0")] pub fn parent_id() -> u32 { From a8d5c6ca9e674c9e573e9a150d96dc3622ce362c Mon Sep 17 00:00:00 2001 From: Seth Junot Date: Sun, 11 Oct 2026 13:18:30 -0700 Subject: [PATCH 11/11] minor: note dylib link and build vars are ideally equal --- src/bootstrap/src/core/build_steps/llvm.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index 6a7760eba693a..8885a7bd89afb 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -557,6 +557,9 @@ impl CommandLineStep for Llvm { // which saves both memory during parallel links and overall disk space // for the tools. We don't do this on every platform as it doesn't work // equally well everywhere. + // + // Under normal circumstances, the dylib LINK and BUILD vars below should + // be set to the same value. See #160597 let llvm_dylib = if link_shared { "ON" } else { "OFF" }; cfg.define("LLVM_LINK_LLVM_DYLIB", llvm_dylib); cfg.define("LLVM_BUILD_LLVM_DYLIB", llvm_dylib);