Skip to content

Make slice comparisons const #143925

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions compiler/rustc_builtin_macros/src/deriving/bounds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub(crate) fn expand_deriving_copy(
methods: Vec::new(),
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};

trait_def.expand(cx, mitem, item, push);
Expand All @@ -46,6 +47,7 @@ pub(crate) fn expand_deriving_const_param_ty(
methods: Vec::new(),
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};

trait_def.expand(cx, mitem, item, push);
Expand All @@ -60,6 +62,7 @@ pub(crate) fn expand_deriving_const_param_ty(
methods: Vec::new(),
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};

trait_def.expand(cx, mitem, item, push);
Expand All @@ -83,6 +86,7 @@ pub(crate) fn expand_deriving_unsized_const_param_ty(
methods: Vec::new(),
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};

trait_def.expand(cx, mitem, item, push);
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_builtin_macros/src/deriving/clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ pub(crate) fn expand_deriving_clone(
}],
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};

trait_def.expand_ext(cx, mitem, item, push, is_simple)
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ pub(crate) fn expand_deriving_eq(
}],
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};
trait_def.expand_ext(cx, mitem, item, push, true)
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub(crate) fn expand_deriving_ord(
}],
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};

trait_def.expand(cx, mitem, item, push)
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_builtin_macros/src/deriving/cmp/partial_eq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub(crate) fn expand_deriving_partial_eq(
methods: Vec::new(),
associated_types: Vec::new(),
is_const: false,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};
structural_trait_def.expand(cx, mitem, item, push);

Expand Down Expand Up @@ -58,6 +59,7 @@ pub(crate) fn expand_deriving_partial_eq(
methods,
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};
trait_def.expand(cx, mitem, item, push)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ pub(crate) fn expand_deriving_partial_ord(
methods: vec![partial_cmp_def],
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};
trait_def.expand(cx, mitem, item, push)
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_builtin_macros/src/deriving/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub(crate) fn expand_deriving_debug(
}],
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};
trait_def.expand(cx, mitem, item, push)
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_builtin_macros/src/deriving/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub(crate) fn expand_deriving_default(
}],
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};
trait_def.expand(cx, mitem, item, push)
}
Expand Down
47 changes: 44 additions & 3 deletions compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,11 @@ use std::{iter, vec};
pub(crate) use StaticFields::*;
pub(crate) use SubstructureFields::*;
use rustc_ast::ptr::P;
use rustc_ast::token::{IdentIsRaw, LitKind, Token, TokenKind};
use rustc_ast::tokenstream::{DelimSpan, Spacing, TokenTree};
use rustc_ast::{
self as ast, AnonConst, BindingMode, ByRef, EnumDef, Expr, GenericArg, GenericParamKind,
Generics, Mutability, PatKind, VariantData,
self as ast, AnonConst, AttrArgs, BindingMode, ByRef, DelimArgs, EnumDef, Expr, GenericArg,
GenericParamKind, Generics, Mutability, PatKind, Safety, VariantData,
};
use rustc_attr_data_structures::{AttributeKind, ReprPacked};
use rustc_attr_parsing::AttributeParser;
Expand Down Expand Up @@ -222,6 +224,8 @@ pub(crate) struct TraitDef<'a> {
pub associated_types: Vec<(Ident, Ty)>,

pub is_const: bool,

pub is_staged_api_crate: bool,
}

pub(crate) struct MethodDef<'a> {
Expand Down Expand Up @@ -784,8 +788,45 @@ impl<'a> TraitDef<'a> {
// Create the type of `self`.
let path = cx.path_all(self.span, false, vec![type_ident], self_params);
let self_type = cx.ty_path(path);
let rustc_const_unstable =
cx.path_ident(self.span, Ident::new(sym::rustc_const_unstable, self.span));

let mut attrs = thin_vec![cx.attr_word(sym::automatically_derived, self.span),];

// Only add `rustc_const_unstable` attributes if `derive_const` is used within libcore/libstd,
// Other crates don't need stability attributes, so adding them is not useful, but libcore needs them
// on all const trait impls.
if self.is_const && self.is_staged_api_crate {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's clearly something interesting going on here, please add a comment explaining what.

Copy link
Member

@fee1-dead fee1-dead Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with this being a temporary solution. To me, we should change #[derive_const(PartialEq)] to something like #[derive(PartialEq(const))] (syntax to-be-bikeshedded), then we can have #[derive(PartialEq(const, unstable))] in all standard library crates.

Copy link
Member

@RalfJung RalfJung Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does it know which feature gate to use...?
We're fairly close to actually enforcing feature stability on impls (#140399) so this question matters soon, IIUC.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it doesn't. All derived const trait impls are under the derive_const feature gate

attrs.push(
cx.attr_nested(
rustc_ast::AttrItem {
unsafety: Safety::Default,
path: rustc_const_unstable,
args: AttrArgs::Delimited(DelimArgs {
dspan: DelimSpan::from_single(self.span),
delim: rustc_ast::token::Delimiter::Parenthesis,
tokens: [
TokenKind::Ident(sym::feature, IdentIsRaw::No),
TokenKind::Eq,
TokenKind::lit(LitKind::Str, sym::derive_const, None),
TokenKind::Comma,
TokenKind::Ident(sym::issue, IdentIsRaw::No),
TokenKind::Eq,
TokenKind::lit(LitKind::Str, sym::derive_const_issue, None),
]
.into_iter()
.map(|kind| {
TokenTree::Token(Token { kind, span: self.span }, Spacing::Alone)
})
.collect(),
}),
tokens: None,
},
self.span,
),
)
}

let attrs = thin_vec![cx.attr_word(sym::automatically_derived, self.span),];
let opt_trait_ref = Some(trait_ref);

cx.item(
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_builtin_macros/src/deriving/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub(crate) fn expand_deriving_hash(
}],
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};

hash_trait_def.expand(cx, mitem, item, push);
Expand Down
10 changes: 8 additions & 2 deletions compiler/rustc_expand/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use rustc_ast::token::Delimiter;
use rustc_ast::tokenstream::TokenStream;
use rustc_ast::util::literal;
use rustc_ast::{
self as ast, AnonConst, AttrVec, BlockCheckMode, Expr, LocalKind, MatchKind, PatKind, UnOp,
attr, token, tokenstream,
self as ast, AnonConst, AttrItem, AttrVec, BlockCheckMode, Expr, LocalKind, MatchKind, PatKind,
UnOp, attr, token, tokenstream,
};
use rustc_span::source_map::Spanned;
use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym};
Expand Down Expand Up @@ -766,4 +766,10 @@ impl<'a> ExtCtxt<'a> {
span,
)
}

// Builds an attribute fully manually.
pub fn attr_nested(&self, inner: AttrItem, span: Span) -> ast::Attribute {
let g = &self.sess.psess.attr_id_generator;
attr::mk_attr_from_item(g, inner, None, ast::AttrStyle::Outer, span)
}
}
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,7 @@ symbols! {
derive,
derive_coerce_pointee,
derive_const,
derive_const_issue: "118304",
derive_default_enum,
derive_smart_pointer,
destruct,
Expand Down
3 changes: 2 additions & 1 deletion library/core/src/cmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,8 @@ pub struct AssertParamIsEq<T: Eq + PointeeSized> {
///
/// assert_eq!(2.cmp(&1), Ordering::Greater);
/// ```
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
#[derive(Clone, Copy, Eq, PartialOrd, Ord, Debug, Hash)]
#[derive_const(PartialEq)]
#[stable(feature = "rust1", since = "1.0.0")]
// This is a lang item only so that `BinOp::Cmp` in MIR can return it.
// It has no special behavior, but does require that the three variants
Expand Down
8 changes: 6 additions & 2 deletions library/core/src/cmp/bytewise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@ use crate::num::NonZero;
/// - Neither `Self` nor `Rhs` have provenance, so integer comparisons are correct.
/// - `<Self as PartialEq<Rhs>>::{eq,ne}` are equivalent to comparing the bytes.
#[rustc_specialization_trait]
pub(crate) unsafe trait BytewiseEq<Rhs = Self>: PartialEq<Rhs> + Sized {}
#[const_trait]
pub(crate) unsafe trait BytewiseEq<Rhs = Self>:
~const PartialEq<Rhs> + Sized
{
}

macro_rules! is_bytewise_comparable {
($($t:ty),+ $(,)?) => {$(
unsafe impl BytewiseEq for $t {}
unsafe impl const BytewiseEq for $t {}
)+};
}

Expand Down
1 change: 1 addition & 0 deletions library/core/src/intrinsics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2208,6 +2208,7 @@ pub const unsafe fn raw_eq<T>(a: &T, b: &T) -> bool;
/// [valid]: crate::ptr#safety
#[rustc_nounwind]
#[rustc_intrinsic]
#[rustc_const_unstable(feature = "const_cmp", issue = "143800")]
pub const unsafe fn compare_bytes(left: *const u8, right: *const u8, bytes: usize) -> i32;

/// See documentation of [`std::hint::black_box`] for details.
Expand Down
2 changes: 2 additions & 0 deletions library/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
#![feature(cfg_select)]
#![feature(cfg_target_has_reliable_f16_f128)]
#![feature(const_carrying_mul_add)]
#![feature(const_cmp)]
#![feature(const_destruct)]
#![feature(const_eval_select)]
#![feature(core_intrinsics)]
Expand Down Expand Up @@ -146,6 +147,7 @@
#![feature(const_trait_impl)]
#![feature(decl_macro)]
#![feature(deprecated_suggestion)]
#![feature(derive_const)]
#![feature(doc_cfg)]
#![feature(doc_cfg_hide)]
#![feature(doc_notable_trait)]
Expand Down
2 changes: 1 addition & 1 deletion library/core/src/macros/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1617,7 +1617,7 @@ pub(crate) mod builtin {
/// See [the reference] for more info.
///
/// [the reference]: ../../../reference/attributes/derive.html
#[unstable(feature = "derive_const", issue = "none")]
#[unstable(feature = "derive_const", issue = "118304")]
#[rustc_builtin_macro]
pub macro derive_const($item:item) {
/* compiler built-in */
Expand Down
5 changes: 3 additions & 2 deletions library/core/src/num/nonzero.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,10 @@ impl<T> UseCloned for NonZero<T> where T: ZeroablePrimitive {}
impl<T> Copy for NonZero<T> where T: ZeroablePrimitive {}

#[stable(feature = "nonzero", since = "1.28.0")]
impl<T> PartialEq for NonZero<T>
#[rustc_const_unstable(feature = "const_cmp", issue = "143800")]
impl<T> const PartialEq for NonZero<T>
where
T: ZeroablePrimitive + PartialEq,
T: ZeroablePrimitive + ~const PartialEq,
{
#[inline]
fn eq(&self, other: &Self) -> bool {
Expand Down
3 changes: 2 additions & 1 deletion library/core/src/option.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2243,7 +2243,8 @@ impl<'a, T> From<&'a mut Option<T>> for Option<&'a mut T> {
#[stable(feature = "rust1", since = "1.0.0")]
impl<T> crate::marker::StructuralPartialEq for Option<T> {}
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: PartialEq> PartialEq for Option<T> {
#[rustc_const_unstable(feature = "const_cmp", issue = "143800")]
impl<T: ~const PartialEq> const PartialEq for Option<T> {
#[inline]
fn eq(&self, other: &Self) -> bool {
// Spelling out the cases explicitly optimizes better than
Expand Down
2 changes: 1 addition & 1 deletion library/core/src/prelude/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ pub use crate::macros::builtin::{
alloc_error_handler, bench, derive, global_allocator, test, test_case,
};

#[unstable(feature = "derive_const", issue = "none")]
#[unstable(feature = "derive_const", issue = "118304")]
pub use crate::macros::builtin::derive_const;

#[unstable(
Expand Down
22 changes: 15 additions & 7 deletions library/core/src/slice/cmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ use crate::num::NonZero;
use crate::ops::ControlFlow;

#[stable(feature = "rust1", since = "1.0.0")]
impl<T, U> PartialEq<[U]> for [T]
#[rustc_const_unstable(feature = "const_cmp", issue = "143800")]
impl<T, U> const PartialEq<[U]> for [T]
where
T: PartialEq<U>,
T: ~const PartialEq<U>,
{
fn eq(&self, other: &[U]) -> bool {
SlicePartialEq::equal(self, other)
Expand Down Expand Up @@ -94,6 +95,8 @@ impl<T: PartialOrd> PartialOrd for [T] {

#[doc(hidden)]
// intermediate trait for specialization of slice's PartialEq
#[const_trait]
#[rustc_const_unstable(feature = "const_cmp", issue = "143800")]
trait SlicePartialEq<B> {
fn equal(&self, other: &[B]) -> bool;

Expand All @@ -103,9 +106,10 @@ trait SlicePartialEq<B> {
}

// Generic slice equality
impl<A, B> SlicePartialEq<B> for [A]
#[rustc_const_unstable(feature = "const_cmp", issue = "143800")]
impl<A, B> const SlicePartialEq<B> for [A]
where
A: PartialEq<B>,
A: ~const PartialEq<B>,
{
default fn equal(&self, other: &[B]) -> bool {
if self.len() != other.len() {
Expand All @@ -115,11 +119,14 @@ where
// Implemented as explicit indexing rather
// than zipped iterators for performance reasons.
// See PR https://github.com/rust-lang/rust/pull/116846
for idx in 0..self.len() {
// FIXME(const_hack): make this a `for idx in 0..self.len()` loop.
let mut idx = 0;
while idx < self.len() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should get a FIXME(const-hack).

// bound checks are optimized away
if self[idx] != other[idx] {
return false;
}
idx += 1;
}

true
Expand All @@ -128,9 +135,10 @@ where

// When each element can be compared byte-wise, we can compare all the bytes
// from the whole size in one call to the intrinsics.
impl<A, B> SlicePartialEq<B> for [A]
#[rustc_const_unstable(feature = "const_cmp", issue = "143800")]
impl<A, B> const SlicePartialEq<B> for [A]
where
A: BytewiseEq<B>,
A: ~const BytewiseEq<B>,
{
fn equal(&self, other: &[B]) -> bool {
if self.len() != other.len() {
Expand Down
3 changes: 2 additions & 1 deletion library/core/src/str/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ impl Ord for str {
}

#[stable(feature = "rust1", since = "1.0.0")]
impl PartialEq for str {
#[rustc_const_unstable(feature = "const_cmp", issue = "143800")]
impl const PartialEq for str {
#[inline]
fn eq(&self, other: &str) -> bool {
self.as_bytes() == other.as_bytes()
Expand Down
2 changes: 1 addition & 1 deletion library/std/src/prelude/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ pub use core::prelude::v1::{
alloc_error_handler, bench, derive, global_allocator, test, test_case,
};

#[unstable(feature = "derive_const", issue = "none")]
#[unstable(feature = "derive_const", issue = "118304")]
pub use core::prelude::v1::derive_const;

// Do not `doc(no_inline)` either.
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/consts/const-compare-bytes-ub.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//@ check-fail

#![feature(core_intrinsics)]
#![feature(core_intrinsics, const_cmp)]
use std::intrinsics::compare_bytes;
use std::mem::MaybeUninit;

Expand Down
2 changes: 1 addition & 1 deletion tests/ui/consts/const-compare-bytes.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//@ run-pass

#![feature(core_intrinsics)]
#![feature(core_intrinsics, const_cmp)]
use std::intrinsics::compare_bytes;

fn main() {
Expand Down
Loading
Loading