Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
49044d0
Add support for trait associated items
GuillaumeGomez Jan 20, 2025
f1649a4
Better handling of paths in link to def feature
GuillaumeGomez Jan 26, 2025
1a6d636
Update to last rustc_hir Visitor changes
GuillaumeGomez Jan 26, 2025
d528a49
Fix panic if an item does not have a body
GuillaumeGomez May 4, 2025
25e767b
Ignore impl associated types in jump to def feature
GuillaumeGomez Aug 10, 2025
15f6d2e
rustdoc: Move HTML-specific attr rendering code into HTML rendering mod
fmease Sep 13, 2025
e9b2c4f
avoid violating `slice::from_raw_parts` safety contract in `Vec::extr…
petrosagg May 15, 2025
5673449
feature: Implement vec_try_remove
BenjaminBrienen Sep 24, 2025
137d4ea
BTreeMap: Don't leak allocators when initializing nodes
cammeresi Sep 20, 2025
88e7977
rustdoc: Slightly clean up attr rendering
fmease Sep 13, 2025
d7d7725
rustdoc: Fully escape link section and export name
fmease Sep 13, 2025
85c193a
rustdoc: hide `#[repr(...)]` if it isn't part of the public ABI
fmease May 17, 2025
185ae69
add doc for `NonZero*` const creation
cptpiepmatz Sep 25, 2025
16474c6
Rollup merge of #116882 - fmease:rustdoc-generalized-priv-repr-heuris…
matthiaskrgr Sep 25, 2025
ea7e61c
Rollup merge of #135771 - GuillaumeGomez:jump-to-def-perf, r=fmease
matthiaskrgr Sep 25, 2025
f99ad93
Rollup merge of #141032 - petrosagg:extract-if-ub, r=joboet
matthiaskrgr Sep 25, 2025
2a441fe
Rollup merge of #146293 - BenjaminBrienen:try_remove, r=joboet
matthiaskrgr Sep 25, 2025
468d12f
Rollup merge of #146859 - cammeresi:btree-alloc-20250920, r=joboet
matthiaskrgr Sep 25, 2025
1ba4ee4
Rollup merge of #146924 - cptpiepmatz:doc-nonzero-const-creation, r=j…
matthiaskrgr Sep 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions library/alloc/src/collections/btree/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,9 @@ pub struct BTreeMap<
root: Option<Root<K, V>>,
length: usize,
/// `ManuallyDrop` to control drop order (needs to be dropped after all the nodes).
// Although some of the accessory types store a copy of the allocator, the nodes do not.
// Because allocations will remain live as long as any copy (like this one) of the allocator
// is live, it's unnecessary to store the allocator in each node.
pub(super) alloc: ManuallyDrop<A>,
// For dropck; the `Box` avoids making the `Unpin` impl more strict than before
_marker: PhantomData<crate::boxed::Box<(K, V), A>>,
Expand Down
12 changes: 10 additions & 2 deletions library/alloc/src/collections/btree/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,11 @@ impl<K, V> NodeRef<marker::Owned, K, V, marker::Leaf> {
}

fn from_new_leaf<A: Allocator + Clone>(leaf: Box<LeafNode<K, V>, A>) -> Self {
NodeRef { height: 0, node: NonNull::from(Box::leak(leaf)), _marker: PhantomData }
// The allocator must be dropped, not leaked. See also `BTreeMap::alloc`.
let (leaf, _alloc) = Box::into_raw_with_allocator(leaf);
// SAFETY: the node was just allocated.
let node = unsafe { NonNull::new_unchecked(leaf) };
NodeRef { height: 0, node, _marker: PhantomData }
}
}

Expand All @@ -243,7 +247,11 @@ impl<K, V> NodeRef<marker::Owned, K, V, marker::Internal> {
height: usize,
) -> Self {
debug_assert!(height > 0);
let node = NonNull::from(Box::leak(internal)).cast();
// The allocator must be dropped, not leaked. See also `BTreeMap::alloc`.
let (internal, _alloc) = Box::into_raw_with_allocator(internal);
// SAFETY: the node was just allocated.
let internal = unsafe { NonNull::new_unchecked(internal) };
let node = internal.cast();
let mut this = NodeRef { height, node, _marker: PhantomData };
this.borrow_mut().correct_all_childrens_parent_links();
this
Expand Down
64 changes: 39 additions & 25 deletions library/alloc/src/vec/extract_if.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,27 +64,37 @@ where
type Item = T;

fn next(&mut self) -> Option<T> {
unsafe {
while self.idx < self.end {
let i = self.idx;
let v = slice::from_raw_parts_mut(self.vec.as_mut_ptr(), self.old_len);
let drained = (self.pred)(&mut v[i]);
// Update the index *after* the predicate is called. If the index
// is updated prior and the predicate panics, the element at this
// index would be leaked.
self.idx += 1;
if drained {
self.del += 1;
return Some(ptr::read(&v[i]));
} else if self.del > 0 {
let del = self.del;
let src: *const T = &v[i];
let dst: *mut T = &mut v[i - del];
ptr::copy_nonoverlapping(src, dst, 1);
while self.idx < self.end {
let i = self.idx;
// SAFETY:
// We know that `i < self.end` from the if guard and that `self.end <= self.old_len` from
// the validity of `Self`. Therefore `i` points to an element within `vec`.
//
// Additionally, the i-th element is valid because each element is visited at most once
// and it is the first time we access vec[i].
//
// Note: we can't use `vec.get_unchecked_mut(i)` here since the precondition for that
// function is that i < vec.len(), but we've set vec's length to zero.
let cur = unsafe { &mut *self.vec.as_mut_ptr().add(i) };
let drained = (self.pred)(cur);
// Update the index *after* the predicate is called. If the index
// is updated prior and the predicate panics, the element at this
// index would be leaked.
self.idx += 1;
if drained {
self.del += 1;
// SAFETY: We never touch this element again after returning it.
return Some(unsafe { ptr::read(cur) });
} else if self.del > 0 {
// SAFETY: `self.del` > 0, so the hole slot must not overlap with current element.
// We use copy for move, and never touch this element again.
unsafe {
let hole_slot = self.vec.as_mut_ptr().add(i - self.del);
ptr::copy_nonoverlapping(cur, hole_slot, 1);
}
}
None
}
None
}

fn size_hint(&self) -> (usize, Option<usize>) {
Expand All @@ -95,14 +105,18 @@ where
#[stable(feature = "extract_if", since = "1.87.0")]
impl<T, F, A: Allocator> Drop for ExtractIf<'_, T, F, A> {
fn drop(&mut self) {
unsafe {
if self.idx < self.old_len && self.del > 0 {
let ptr = self.vec.as_mut_ptr();
let src = ptr.add(self.idx);
let dst = src.sub(self.del);
let tail_len = self.old_len - self.idx;
src.copy_to(dst, tail_len);
if self.del > 0 {
// SAFETY: Trailing unchecked items must be valid since we never touch them.
unsafe {
ptr::copy(
self.vec.as_ptr().add(self.idx),
self.vec.as_mut_ptr().add(self.idx - self.del),
self.old_len - self.idx,
);
}
}
// SAFETY: After filling holes, all items are in contiguous memory.
unsafe {
self.vec.set_len(self.old_len - self.del);
}
}
Expand Down
32 changes: 30 additions & 2 deletions library/alloc/src/vec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2173,9 +2173,37 @@ impl<T, A: Allocator> Vec<T, A> {
panic!("removal index (is {index}) should be < len (is {len})");
}

match self.try_remove(index) {
Some(elem) => elem,
None => assert_failed(index, self.len()),
}
}

/// Remove and return the element at position `index` within the vector,
/// shifting all elements after it to the left, or [`None`] if it does not
/// exist.
///
/// Note: Because this shifts over the remaining elements, it has a
/// worst-case performance of *O*(*n*). If you'd like to remove
/// elements from the beginning of the `Vec`, consider using
/// [`VecDeque::pop_front`] instead.
///
/// [`VecDeque::pop_front`]: crate::collections::VecDeque::pop_front
///
/// # Examples
///
/// ```
/// #![feature(vec_try_remove)]
/// let mut v = vec![1, 2, 3];
/// assert_eq!(v.try_remove(0), Some(1));
/// assert_eq!(v.try_remove(2), None);
/// ```
#[unstable(feature = "vec_try_remove", issue = "146954")]
#[rustc_confusables("delete", "take", "remove")]
pub fn try_remove(&mut self, index: usize) -> Option<T> {
let len = self.len();
if index >= len {
assert_failed(index, len);
return None;
}
unsafe {
// infallible
Expand All @@ -2191,7 +2219,7 @@ impl<T, A: Allocator> Vec<T, A> {
ptr::copy(ptr.add(1), ptr, len - index - 1);
}
self.set_len(len - 1);
ret
Some(ret)
}
}

Expand Down
1 change: 1 addition & 0 deletions library/alloctests/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#![feature(unique_rc_arc)]
#![feature(macro_metavar_expr_concat)]
#![feature(vec_peek_mut)]
#![feature(vec_try_remove)]
#![allow(internal_features)]
#![deny(fuzzy_provenance_casts)]
#![deny(unsafe_op_in_unsafe_fn)]
Expand Down
15 changes: 15 additions & 0 deletions library/alloctests/tests/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,21 @@ fn test_swap_remove_empty() {
vec.swap_remove(0);
}

#[test]
fn test_try_remove() {
let mut vec = vec![1, 2, 3];
// We are attempting to remove vec[0] which contains 1
assert_eq!(vec.try_remove(0), Some(1));
// Now `vec` looks like: [2, 3]
// We will now try to remove vec[2] which does not exist
// This should return `None`
assert_eq!(vec.try_remove(2), None);

// We will try the same thing with an empty vector
let mut v: Vec<u8> = vec![];
assert!(v.try_remove(0).is_none());
}

#[test]
fn test_move_items() {
let vec = vec![1, 2, 3];
Expand Down
12 changes: 12 additions & 0 deletions library/core/src/num/nonzero.rs
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,18 @@ macro_rules! nonzero_integer {
#[doc = concat!("assert_eq!(align_of::<", stringify!($Ty), ">(), align_of::<Option<", stringify!($Ty), ">>());")]
/// ```
///
/// # Compile-time creation
///
/// Since both [`Option::unwrap()`] and [`Option::expect()`] are `const`, it is possible to
/// define a new
#[doc = concat!("`", stringify!($Ty), "`")]
/// at compile time via:
/// ```
#[doc = concat!("use std::num::", stringify!($Ty), ";")]
///
#[doc = concat!("const TEN: ", stringify!($Ty), " = ", stringify!($Ty) , r#"::new(10).expect("ten is non-zero");"#)]
/// ```
///
/// [null pointer optimization]: crate::option#representation
#[$stability]
pub type $Ty = NonZero<$Int>;
Expand Down
20 changes: 15 additions & 5 deletions src/doc/rustdoc/src/advanced-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,20 +89,30 @@ https://doc.rust-lang.org/stable/std/?search=%s&go_to_first=true
This URL adds the `go_to_first=true` query parameter which can be appended to any `rustdoc` search URL
to automatically go to the first result.

## `#[repr(transparent)]`: Documenting the transparent representation
## `#[repr(...)]`: Documenting the representation of a type

Generally, rustdoc only displays the representation of a given type if none of its variants are
`#[doc(hidden)]` and if all of its fields are public and not `#[doc(hidden)]` since it's likely
not meant to be considered part of the public ABI otherwise.

Note that there's no way to overwrite that heuristic and force rustdoc to show the representation
regardless.

### `#[repr(transparent)]`

You can read more about `#[repr(transparent)]` itself in the [Rust Reference][repr-trans-ref] and
in the [Rustonomicon][repr-trans-nomicon].

Since this representation is only considered part of the public ABI if the single field with non-trivial
size or alignment is public and if the documentation does not state otherwise, Rustdoc helpfully displays
the attribute if and only if the non-1-ZST field is public or at least one field is public in case all
fields are 1-ZST fields. The term *1-ZST* refers to types that are one-aligned and zero-sized.
size or alignment is public and if the documentation does not state otherwise, rustdoc helpfully displays
the attribute if and only if the non-1-ZST field is public and not `#[doc(hidden)]` or
– in case all fields are 1-ZST fields — at least one field is public and not `#[doc(hidden)]`.
The term *1-ZST* refers to types that are one-aligned and zero-sized.

It would seem that one can manually hide the attribute with `#[cfg_attr(not(doc), repr(transparent))]`
if one wishes to declare the representation as private even if the non-1-ZST field is public.
However, due to [current limitations][cross-crate-cfg-doc], this method is not always guaranteed to work.
Therefore, if you would like to do so, you should always write it down in prose independently of whether
Therefore, if you would like to do so, you should always write that down in prose independently of whether
you use `cfg_attr` or not.

[repr-trans-ref]: https://doc.rust-lang.org/reference/type-layout.html#the-transparent-representation
Expand Down
112 changes: 0 additions & 112 deletions src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -794,50 +794,6 @@ impl Item {
Some(tcx.visibility(def_id))
}

/// Get a list of attributes excluding `#[repr]` to display.
///
/// Only used by the HTML output-format.
fn attributes_without_repr(&self) -> Vec<String> {
self.attrs
.other_attrs
.iter()
.filter_map(|attr| match attr {
hir::Attribute::Parsed(AttributeKind::LinkSection { name, .. }) => {
Some(format!("#[unsafe(link_section = \"{name}\")]"))
}
hir::Attribute::Parsed(AttributeKind::NoMangle(..)) => {
Some("#[unsafe(no_mangle)]".to_string())
}
hir::Attribute::Parsed(AttributeKind::ExportName { name, .. }) => {
Some(format!("#[unsafe(export_name = \"{name}\")]"))
}
hir::Attribute::Parsed(AttributeKind::NonExhaustive(..)) => {
Some("#[non_exhaustive]".to_string())
}
_ => None,
})
.collect()
}

/// Get a list of attributes to display on this item.
///
/// Only used by the HTML output-format.
pub(crate) fn attributes(&self, tcx: TyCtxt<'_>, cache: &Cache) -> Vec<String> {
let mut attrs = self.attributes_without_repr();

if let Some(repr_attr) = self.repr(tcx, cache) {
attrs.push(repr_attr);
}
attrs
}

/// Returns a stringified `#[repr(...)]` attribute.
///
/// Only used by the HTML output-format.
pub(crate) fn repr(&self, tcx: TyCtxt<'_>, cache: &Cache) -> Option<String> {
repr_attributes(tcx, cache, self.def_id()?, self.type_())
}

pub fn is_doc_hidden(&self) -> bool {
self.attrs.is_doc_hidden()
}
Expand All @@ -847,74 +803,6 @@ impl Item {
}
}

/// Return a string representing the `#[repr]` attribute if present.
///
/// Only used by the HTML output-format.
pub(crate) fn repr_attributes(
tcx: TyCtxt<'_>,
cache: &Cache,
def_id: DefId,
item_type: ItemType,
) -> Option<String> {
use rustc_abi::IntegerType;

if !matches!(item_type, ItemType::Struct | ItemType::Enum | ItemType::Union) {
return None;
}
let adt = tcx.adt_def(def_id);
let repr = adt.repr();
let mut out = Vec::new();
if repr.c() {
out.push("C");
}
if repr.transparent() {
// Render `repr(transparent)` iff the non-1-ZST field is public or at least one
// field is public in case all fields are 1-ZST fields.
let render_transparent = cache.document_private
|| adt
.all_fields()
.find(|field| {
let ty = field.ty(tcx, ty::GenericArgs::identity_for_item(tcx, field.did));
tcx.layout_of(ty::TypingEnv::post_analysis(tcx, field.did).as_query_input(ty))
.is_ok_and(|layout| !layout.is_1zst())
})
.map_or_else(
|| adt.all_fields().any(|field| field.vis.is_public()),
|field| field.vis.is_public(),
);

if render_transparent {
out.push("transparent");
}
}
if repr.simd() {
out.push("simd");
}
let pack_s;
if let Some(pack) = repr.pack {
pack_s = format!("packed({})", pack.bytes());
out.push(&pack_s);
}
let align_s;
if let Some(align) = repr.align {
align_s = format!("align({})", align.bytes());
out.push(&align_s);
}
let int_s;
if let Some(int) = repr.int {
int_s = match int {
IntegerType::Pointer(is_signed) => {
format!("{}size", if is_signed { 'i' } else { 'u' })
}
IntegerType::Fixed(size, is_signed) => {
format!("{}{}", if is_signed { 'i' } else { 'u' }, size.size().bytes() * 8)
}
};
out.push(&int_s);
}
if !out.is_empty() { Some(format!("#[repr({})]", out.join(", "))) } else { None }
}

#[derive(Clone, Debug)]
pub(crate) enum ItemKind {
ExternCrateItem {
Expand Down
Loading