From 461804a1eadc156b1583cbc9d7023371433925a7 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 4 Aug 2026 16:55:31 +0200 Subject: [PATCH 1/2] Extend `NumBuffer` API to allow casting --- library/core/src/fmt/num_buffer.rs | 59 +++++++++++++++++++++++- library/coretests/tests/fmt/mod.rs | 1 + library/coretests/tests/fmt/numbuffer.rs | 21 +++++++++ tests/ui/numeric/numbuffer-case.rs | 16 +++++++ tests/ui/numeric/numbuffer-case.stderr | 21 +++++++++ 5 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 library/coretests/tests/fmt/numbuffer.rs create mode 100644 tests/ui/numeric/numbuffer-case.rs create mode 100644 tests/ui/numeric/numbuffer-case.stderr diff --git a/library/core/src/fmt/num_buffer.rs b/library/core/src/fmt/num_buffer.rs index 7d42091b05086..9cb1dca4c6787 100644 --- a/library/core/src/fmt/num_buffer.rs +++ b/library/core/src/fmt/num_buffer.rs @@ -56,8 +56,8 @@ impl_NumBufferTrait! { /// assert_eq!(n2.format_into(&mut buf), "-1972"); /// ``` #[stable(feature = "int_format_into", since = "1.98.0")] -pub struct NumBuffer { - pub(crate) buf: T::Buf, +pub struct NumBuffer::Buf> { + pub(crate) buf: B, phantom: core::marker::PhantomData, } @@ -77,3 +77,58 @@ impl NumBuffer { NumBuffer { buf: T::DEFAULT, phantom: core::marker::PhantomData } } } + +impl]>> NumBuffer { + /// Allows to cast between `NumBuffer` types at compile-time without new allocation. If you want + /// to cast to a `NumBuffer` of a bigger size (because it comes from a downcast), you'll want to + /// use [`cast_into`](Self::cast_into) instead. + #[unstable(feature = "fmt_internals", issue = "none")] + #[rustc_const_unstable(feature = "fmt_internals", issue = "none")] + #[track_caller] + pub const fn const_cast_into(&mut self) -> &mut NumBuffer { + const { + assert!( + core::mem::size_of::() >= core::mem::size_of::(), + "target `NumBuffer` size must smaller or equal to source `NumBuffer` size" + ); + } + // SAFETY: The target `NumBuffer` buffer is not bigger so this conversion is ok. + unsafe { core::mem::transmute::<&mut NumBuffer, &mut NumBuffer>(self) } + } + + /// Allows to cast between `NumBuffer` as long as the internal buffer size of the target + /// `NumBuffer` is not bigger than the current one. Returns `None` otherwise. + /// + /// If you want to cast to a buffer of a smaller size, + /// [`const_cast_into`](Self::const_cast_into) is likely always a better idea. + /// + /// # Examples + /// + /// ``` + /// use core::fmt::NumBuffer; + /// + /// let mut buf = NumBuffer::::new(); + /// + /// assert_eq!(16u16.format_into(buf.cast_into::()), "16"); + /// assert_eq!(u16::MAX.format_into(buf.cast_into::()), u16::MAX.to_string()); + /// + /// assert_eq!(-16i16.format_into(buf.cast_into::()), "-16"); + /// assert_eq!(i16::MIN.format_into(buf.cast_into::()), i16::MIN.to_string()); + /// + /// // Cannot work since `u64` requires a bigger buffer. + /// assert!(buf.cast_into::(), None); + /// // Cannot work since `i32` requires a bigger buffer (because of the `-` sign). + /// assert!(buf.cast_into::(), None); + /// ``` + #[unstable(feature = "fmt_internals", issue = "none")] + pub fn cast_into(&mut self) -> Option<&mut NumBuffer> { + if self.buf.as_ref().len() >= core::mem::size_of::() { + // SAFETY: The target `NumBuffer` buffer is not bigger so this conversion is ok. + Some(unsafe { + core::mem::transmute::<&mut NumBuffer, &mut NumBuffer>(self) + }) + } else { + None + } + } +} diff --git a/library/coretests/tests/fmt/mod.rs b/library/coretests/tests/fmt/mod.rs index 67ba49db4d919..6e9df780acb61 100644 --- a/library/coretests/tests/fmt/mod.rs +++ b/library/coretests/tests/fmt/mod.rs @@ -1,6 +1,7 @@ mod builders; mod float; mod num; +mod numbuffer; #[test] fn test_lifetime() { diff --git a/library/coretests/tests/fmt/numbuffer.rs b/library/coretests/tests/fmt/numbuffer.rs new file mode 100644 index 0000000000000..f9467aa1323ed --- /dev/null +++ b/library/coretests/tests/fmt/numbuffer.rs @@ -0,0 +1,21 @@ +use core::fmt::NumBuffer; +use std::mem::size_of_val; + +// This test ensures that the `NumBuffer` size and its buffer length doesn't change through +// conversions. +#[test] +fn test_numberbuffer_size() { + let mut x = NumBuffer::::new(); + let x_size = size_of_val(&x); + let y = x.const_cast_into::(); + let y_size = size_of_val(y); + // Should work since we come from a bigger buffer originally. + let z = y.cast_into::().unwrap(); + let z_size = size_of_val(z); + + assert_eq!(x_size, y_size); + assert_eq!(x_size, z_size); + + let x = NumBuffer::::new(); + assert!(x_size < size_of_val(&x)); +} diff --git a/tests/ui/numeric/numbuffer-case.rs b/tests/ui/numeric/numbuffer-case.rs new file mode 100644 index 0000000000000..b9be89d3d102b --- /dev/null +++ b/tests/ui/numeric/numbuffer-case.rs @@ -0,0 +1,16 @@ +// Test to ensure that you cannot use `const_cast_into` to convert a `NumBuffer` to +// a bigger buffer. + +//@ build-fail + +#![feature(fmt_internals)] + +extern crate core; + +use core::fmt::NumBuffer; + +fn main() { + let mut x = NumBuffer::::new(); + let mut y = x.const_cast_into::(); + //~? ERROR: target `NumBuffer` size must smaller or equal to source `NumBuffer` size +} diff --git a/tests/ui/numeric/numbuffer-case.stderr b/tests/ui/numeric/numbuffer-case.stderr new file mode 100644 index 0000000000000..756a9eb9a4baf --- /dev/null +++ b/tests/ui/numeric/numbuffer-case.stderr @@ -0,0 +1,21 @@ +error[E0080]: evaluation panicked: target `NumBuffer` size must smaller or equal to source `NumBuffer` size + --> $SRC_DIR/core/src/panic.rs:LL:COL + | + = note: evaluation of `core::fmt::NumBuffer::; 10]>::const_cast_into::::{constant#0}` failed here + --> $SRC_DIR/core/src/fmt/num_buffer.rs:LL:COL + ::: $SRC_DIR/core/src/fmt/num_buffer.rs:LL:COL + | + = note: in this macro invocation + +note: erroneous constant encountered + --> $SRC_DIR/core/src/fmt/num_buffer.rs:LL:COL + +note: the above error was encountered while instantiating `fn NumBuffer::; 10]>::const_cast_into::` + --> $DIR/numbuffer-case.rs:14:17 + | +LL | let mut y = x.const_cast_into::(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0080`. From d1cabe751275301fdd1a0aaaf714f1b11baa3e71 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 4 Aug 2026 22:26:37 +0200 Subject: [PATCH 2/2] Only unidirectional cast for `NumBuffer` --- library/core/src/fmt/num_buffer.rs | 59 +++++++++--------------- library/coretests/tests/fmt/mod.rs | 1 - library/coretests/tests/fmt/numbuffer.rs | 21 --------- tests/ui/numeric/numbuffer-case.rs | 6 +-- tests/ui/numeric/numbuffer-case.stderr | 10 ++-- 5 files changed, 31 insertions(+), 66 deletions(-) delete mode 100644 library/coretests/tests/fmt/numbuffer.rs diff --git a/library/core/src/fmt/num_buffer.rs b/library/core/src/fmt/num_buffer.rs index 9cb1dca4c6787..e224878466916 100644 --- a/library/core/src/fmt/num_buffer.rs +++ b/library/core/src/fmt/num_buffer.rs @@ -56,8 +56,9 @@ impl_NumBufferTrait! { /// assert_eq!(n2.format_into(&mut buf), "-1972"); /// ``` #[stable(feature = "int_format_into", since = "1.98.0")] -pub struct NumBuffer::Buf> { - pub(crate) buf: B, +#[repr(transparent)] +pub struct NumBuffer { + pub(crate) buf: T::Buf, phantom: core::marker::PhantomData, } @@ -78,29 +79,8 @@ impl NumBuffer { } } -impl]>> NumBuffer { - /// Allows to cast between `NumBuffer` types at compile-time without new allocation. If you want - /// to cast to a `NumBuffer` of a bigger size (because it comes from a downcast), you'll want to - /// use [`cast_into`](Self::cast_into) instead. - #[unstable(feature = "fmt_internals", issue = "none")] - #[rustc_const_unstable(feature = "fmt_internals", issue = "none")] - #[track_caller] - pub const fn const_cast_into(&mut self) -> &mut NumBuffer { - const { - assert!( - core::mem::size_of::() >= core::mem::size_of::(), - "target `NumBuffer` size must smaller or equal to source `NumBuffer` size" - ); - } - // SAFETY: The target `NumBuffer` buffer is not bigger so this conversion is ok. - unsafe { core::mem::transmute::<&mut NumBuffer, &mut NumBuffer>(self) } - } - - /// Allows to cast between `NumBuffer` as long as the internal buffer size of the target - /// `NumBuffer` is not bigger than the current one. Returns `None` otherwise. - /// - /// If you want to cast to a buffer of a smaller size, - /// [`const_cast_into`](Self::const_cast_into) is likely always a better idea. +impl NumBuffer { + /// Allows to cast between `NumBuffer` types at compile-time without new allocation. /// /// # Examples /// @@ -114,21 +94,28 @@ impl]>> NumBuffer { /// /// assert_eq!(-16i16.format_into(buf.cast_into::()), "-16"); /// assert_eq!(i16::MIN.format_into(buf.cast_into::()), i16::MIN.to_string()); + /// ``` + /// + /// If you try to cast to a `NumBuffer` with a bigger buffer size, it will not compile: + /// + /// ```compile_fail + /// use core::fmt::NumBuffer; /// - /// // Cannot work since `u64` requires a bigger buffer. - /// assert!(buf.cast_into::(), None); + /// let mut buf = NumBuffer::::new(); /// // Cannot work since `i32` requires a bigger buffer (because of the `-` sign). - /// assert!(buf.cast_into::(), None); + /// let buf = buf.cast_into::(); /// ``` #[unstable(feature = "fmt_internals", issue = "none")] - pub fn cast_into(&mut self) -> Option<&mut NumBuffer> { - if self.buf.as_ref().len() >= core::mem::size_of::() { - // SAFETY: The target `NumBuffer` buffer is not bigger so this conversion is ok. - Some(unsafe { - core::mem::transmute::<&mut NumBuffer, &mut NumBuffer>(self) - }) - } else { - None + #[rustc_const_unstable(feature = "fmt_internals", issue = "none")] + #[track_caller] + pub const fn cast_into(&mut self) -> &mut NumBuffer { + const { + assert!( + core::mem::size_of::() >= core::mem::size_of::(), + "target `NumBuffer` size must be smaller or equal to source `NumBuffer` size" + ); } + // SAFETY: The target `NumBuffer` buffer is not bigger so this conversion is ok. + unsafe { core::mem::transmute::<&mut NumBuffer, &mut NumBuffer>(self) } } } diff --git a/library/coretests/tests/fmt/mod.rs b/library/coretests/tests/fmt/mod.rs index 6e9df780acb61..67ba49db4d919 100644 --- a/library/coretests/tests/fmt/mod.rs +++ b/library/coretests/tests/fmt/mod.rs @@ -1,7 +1,6 @@ mod builders; mod float; mod num; -mod numbuffer; #[test] fn test_lifetime() { diff --git a/library/coretests/tests/fmt/numbuffer.rs b/library/coretests/tests/fmt/numbuffer.rs deleted file mode 100644 index f9467aa1323ed..0000000000000 --- a/library/coretests/tests/fmt/numbuffer.rs +++ /dev/null @@ -1,21 +0,0 @@ -use core::fmt::NumBuffer; -use std::mem::size_of_val; - -// This test ensures that the `NumBuffer` size and its buffer length doesn't change through -// conversions. -#[test] -fn test_numberbuffer_size() { - let mut x = NumBuffer::::new(); - let x_size = size_of_val(&x); - let y = x.const_cast_into::(); - let y_size = size_of_val(y); - // Should work since we come from a bigger buffer originally. - let z = y.cast_into::().unwrap(); - let z_size = size_of_val(z); - - assert_eq!(x_size, y_size); - assert_eq!(x_size, z_size); - - let x = NumBuffer::::new(); - assert!(x_size < size_of_val(&x)); -} diff --git a/tests/ui/numeric/numbuffer-case.rs b/tests/ui/numeric/numbuffer-case.rs index b9be89d3d102b..af393b832a1ee 100644 --- a/tests/ui/numeric/numbuffer-case.rs +++ b/tests/ui/numeric/numbuffer-case.rs @@ -1,4 +1,4 @@ -// Test to ensure that you cannot use `const_cast_into` to convert a `NumBuffer` to +// Test to ensure that you cannot use `cast_into` to convert a `NumBuffer` to // a bigger buffer. //@ build-fail @@ -11,6 +11,6 @@ use core::fmt::NumBuffer; fn main() { let mut x = NumBuffer::::new(); - let mut y = x.const_cast_into::(); - //~? ERROR: target `NumBuffer` size must smaller or equal to source `NumBuffer` size + let mut y = x.cast_into::(); + //~? ERROR: target `NumBuffer` size must be smaller or equal to source `NumBuffer` size } diff --git a/tests/ui/numeric/numbuffer-case.stderr b/tests/ui/numeric/numbuffer-case.stderr index 756a9eb9a4baf..86d31f95b8540 100644 --- a/tests/ui/numeric/numbuffer-case.stderr +++ b/tests/ui/numeric/numbuffer-case.stderr @@ -1,7 +1,7 @@ -error[E0080]: evaluation panicked: target `NumBuffer` size must smaller or equal to source `NumBuffer` size +error[E0080]: evaluation panicked: target `NumBuffer` size must be smaller or equal to source `NumBuffer` size --> $SRC_DIR/core/src/panic.rs:LL:COL | - = note: evaluation of `core::fmt::NumBuffer::; 10]>::const_cast_into::::{constant#0}` failed here + = note: evaluation of `core::fmt::NumBuffer::::cast_into::::{constant#0}` failed here --> $SRC_DIR/core/src/fmt/num_buffer.rs:LL:COL ::: $SRC_DIR/core/src/fmt/num_buffer.rs:LL:COL | @@ -10,11 +10,11 @@ error[E0080]: evaluation panicked: target `NumBuffer` size must smaller or equal note: erroneous constant encountered --> $SRC_DIR/core/src/fmt/num_buffer.rs:LL:COL -note: the above error was encountered while instantiating `fn NumBuffer::; 10]>::const_cast_into::` +note: the above error was encountered while instantiating `fn NumBuffer::::cast_into::` --> $DIR/numbuffer-case.rs:14:17 | -LL | let mut y = x.const_cast_into::(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | let mut y = x.cast_into::(); + | ^^^^^^^^^^^^^^^^^^^^ error: aborting due to 1 previous error