Skip to content
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
42 changes: 42 additions & 0 deletions library/core/src/fmt/num_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ impl_NumBufferTrait! {
/// assert_eq!(n2.format_into(&mut buf), "-1972");
/// ```
#[stable(feature = "int_format_into", since = "1.98.0")]
#[repr(transparent)]
pub struct NumBuffer<T: NumBufferTrait> {
pub(crate) buf: T::Buf,
phantom: core::marker::PhantomData<T>,
Expand All @@ -77,3 +78,44 @@ impl<T: NumBufferTrait> NumBuffer<T> {
NumBuffer { buf: T::DEFAULT, phantom: core::marker::PhantomData }
}
}

impl<T: NumBufferTrait> NumBuffer<T> {
/// Allows to cast between `NumBuffer` types at compile-time without new allocation.
///
/// # Examples
///
/// ```
/// use core::fmt::NumBuffer;
///
/// let mut buf = NumBuffer::<u32>::new();
///
/// assert_eq!(16u16.format_into(buf.cast_into::<u16>()), "16");
/// assert_eq!(u16::MAX.format_into(buf.cast_into::<u16>()), u16::MAX.to_string());
///
/// assert_eq!(-16i16.format_into(buf.cast_into::<i16>()), "-16");
/// assert_eq!(i16::MIN.format_into(buf.cast_into::<i16>()), 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;
///
/// let mut buf = NumBuffer::<u32>::new();
/// // Cannot work since `i32` requires a bigger buffer (because of the `-` sign).
/// let buf = buf.cast_into::<i32>();
/// ```
#[unstable(feature = "fmt_internals", issue = "none")]
#[rustc_const_unstable(feature = "fmt_internals", issue = "none")]
#[track_caller]
pub const fn cast_into<U: NumBufferTrait>(&mut self) -> &mut NumBuffer<U> {

@joshtriplett joshtriplett Aug 25, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This shouldn't be called cast_into, since it operates on &mut; it should just be cast.

View changes since the review

const {
assert!(
core::mem::size_of::<T::Buf>() >= core::mem::size_of::<U::Buf>(),
"target `NumBuffer` size must be smaller or equal to source `NumBuffer` size"
);
Comment thread
dtolnay marked this conversation as resolved.
}
// SAFETY: The target `NumBuffer` buffer is not bigger so this conversion is ok.
unsafe { core::mem::transmute::<&mut NumBuffer<T>, &mut NumBuffer<U>>(self) }

@programmerjake programmerjake Aug 4, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

for the transmute to be valid, NumBuffer needs to be repr(transparent) or some other well-defined repr, repr(Rust) isn't really. Also, NumBufferTrait should be unsafe and require Buf to be an array of MaybeUninit<u8>.

View changes since the review

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good point! Gonna add the repr(transparent).

It's a conversion between NumBuffer, not part of NumBufferTrait, so not sure there is any benefit in enforcing Buf to be an array of MaybeUninit<u8>.

@programmerjake programmerjake Aug 4, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

the transmute's safety depends on Buf being a transmutable type that behaves like an array of bytes, so unless the trait is unsafe, third party code can do:

impl NumBufferTrait for MyType1 {
    type Buf = [MaybeUninit<u8>; 8];
}

impl NumBufTrait for MyType2 {
    type Buf = Box<[u8; 1]>;
}

and then use that transmute to convert unsoundly from one to the other which allows reading/writing to nearly arbitrary addresses from safe code.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

NumBufferTrait is not implementable outside of core for now, but that's a good concern in case we allow it in the future (which would be nice, pattern type incoming!).

How would you make the trait work with the Buf type being different (although still an array) for all integers (and eventually floats at some point?). The size of the array is different for each integer after all.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

oh, I was mostly just saying that we'd require the implementer via. unsafe to have Buf be [MaybeUninit<u8>; N] for some N.

in the future we could migrate to using type-level constants (previously spelled type const but I think that's been changed):

pub trait NumBufferTrait {
    type const BUF_SIZE: usize;
}

#[repr(transparent)]
pub struct NumBuffer<T: NumBufferTrait> {
    buf: [MaybeUninit<u8>; T::BUF_SIZE],
    _phantom: PhantomData<fn(T)>,
}

}
}
16 changes: 16 additions & 0 deletions tests/ui/numeric/numbuffer-case.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Test to ensure that you cannot use `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::<u32>::new();
let mut y = x.cast_into::<u64>();
//~? ERROR: target `NumBuffer` size must be smaller or equal to source `NumBuffer` size
}
21 changes: 21 additions & 0 deletions tests/ui/numeric/numbuffer-case.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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::<u32>::cast_into::<u64>::{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::<u32>::cast_into::<u64>`
--> $DIR/numbuffer-case.rs:14:17
|
LL | let mut y = x.cast_into::<u64>();
| ^^^^^^^^^^^^^^^^^^^^

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0080`.
Loading