Skip to content

Commit d317da4

Browse files
authored
Rollup merge of #91325 - RalfJung:const_eval_select, r=dtolnay
adjust const_eval_select documentation "The Rust compiler assumes" indicates that this is language UB, but [I don't think that is a good idea](https://rust-lang.zulipchat.com/#narrow/stream/146212-t-compiler.2Fconst-eval/topic/const_eval_select.20assumptions). This UB would be very hard to test for and looks like a way-too-big footgun. ``@oli-obk`` suggested this is meant to be more like "library UB", so I tried to adjust the docs accordingly. I also removed all references to "referential transparency". That is a rather vague concept used to mean many different things, and I honestly have no idea what exactly is meant by it in this specific instance. But I assume ``@fee1-dead`` had in their mind a property that all `const fn` code upholds, so by demanding that the runtime code and the const-time code are *observably equivalent*, whatever that property is would also be enforced here. Cc ``@rust-lang/wg-const-eval``
2 parents 60aa03a + 85558ad commit d317da4

File tree

2 files changed

+37
-16
lines changed

2 files changed

+37
-16
lines changed

library/core/src/intrinsics.rs

+35-14
Original file line numberDiff line numberDiff line change
@@ -2070,8 +2070,8 @@ pub const unsafe fn copy_nonoverlapping<T>(src: *const T, dst: *mut T, count: us
20702070
#[cfg(debug_assertions)]
20712071
const fn compiletime_check<T>(_src: *const T, _dst: *mut T, _count: usize) {}
20722072
#[cfg(debug_assertions)]
2073-
// SAFETY: runtime debug-assertions are a best-effort basis; it's fine to
2074-
// not do them during compile time
2073+
// SAFETY: As per our safety precondition, we may assume that the `abort` above is never reached.
2074+
// Therefore, compiletime_check and runtime_check are observably equivalent.
20752075
unsafe {
20762076
const_eval_select((src, dst, count), compiletime_check, runtime_check);
20772077
}
@@ -2161,8 +2161,8 @@ pub const unsafe fn copy<T>(src: *const T, dst: *mut T, count: usize) {
21612161
#[cfg(debug_assertions)]
21622162
const fn compiletime_check<T>(_src: *const T, _dst: *mut T) {}
21632163
#[cfg(debug_assertions)]
2164-
// SAFETY: runtime debug-assertions are a best-effort basis; it's fine to
2165-
// not do them during compile time
2164+
// SAFETY: As per our safety precondition, we may assume that the `abort` above is never reached.
2165+
// Therefore, compiletime_check and runtime_check are observably equivalent.
21662166
unsafe {
21672167
const_eval_select((src, dst), compiletime_check, runtime_check);
21682168
}
@@ -2273,19 +2273,40 @@ pub unsafe fn write_bytes<T>(dst: *mut T, val: u8, count: usize) {
22732273
///
22742274
/// # Safety
22752275
///
2276-
/// This intrinsic allows breaking [referential transparency] in `const fn`
2277-
/// and is therefore `unsafe`.
2276+
/// The two functions must behave observably equivalent. Safe code in other
2277+
/// crates may assume that calling a `const fn` at compile-time and at run-time
2278+
/// produces the same result. A function that produces a different result when
2279+
/// evaluated at run-time, or has any other observable side-effects, is
2280+
/// *unsound*.
22782281
///
2279-
/// Code that uses this intrinsic must be extremely careful to ensure that
2280-
/// `const fn`s remain referentially-transparent independently of when they
2281-
/// are evaluated.
2282+
/// Here is an example of how this could cause a problem:
2283+
/// ```no_run
2284+
/// #![feature(const_eval_select)]
2285+
/// use std::hint::unreachable_unchecked;
2286+
/// use std::intrinsics::const_eval_select;
22822287
///
2283-
/// The Rust compiler assumes that it is sound to replace a call to a `const
2284-
/// fn` with the result produced by evaluating it at compile-time. If
2285-
/// evaluating the function at run-time were to produce a different result,
2286-
/// or have any other observable side-effects, the behavior is undefined.
2288+
/// // Crate A
2289+
/// pub const fn inconsistent() -> i32 {
2290+
/// fn runtime() -> i32 { 1 }
2291+
/// const fn compiletime() -> i32 { 2 }
22872292
///
2288-
/// [referential transparency]: https://en.wikipedia.org/wiki/Referential_transparency
2293+
/// unsafe {
2294+
// // ⚠ This code violates the required equivalence of `compiletime`
2295+
/// // and `runtime`.
2296+
/// const_eval_select((), compiletime, runtime)
2297+
/// }
2298+
/// }
2299+
///
2300+
/// // Crate B
2301+
/// const X: i32 = inconsistent();
2302+
/// let x = inconsistent();
2303+
/// if x != X { unsafe { unreachable_unchecked(); }}
2304+
/// ```
2305+
///
2306+
/// This code causes Undefined Behavior when being run, since the
2307+
/// `unreachable_unchecked` is actually being reached. The bug is in *crate A*,
2308+
/// which violates the principle that a `const fn` must behave the same at
2309+
/// compile-time and at run-time. The unsafe code in crate B is fine.
22892310
#[unstable(
22902311
feature = "const_eval_select",
22912312
issue = "none",

library/core/src/slice/raw.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ const fn debug_check_data_len<T>(data: *const T, len: usize) {
149149
// it is not required for safety (the safety must be guatanteed by
150150
// the `from_raw_parts[_mut]` caller).
151151
//
152-
// Since the checks are not required, we ignore them in CTFE as they can't
153-
// be done there (alignment does not make much sense there).
152+
// As per our safety precondition, we may assume that assertion above never fails.
153+
// Therefore, noop and rt_check are observably equivalent.
154154
unsafe {
155155
crate::intrinsics::const_eval_select((data,), noop, rt_check);
156156
}

0 commit comments

Comments
 (0)