Skip to content

Commit 1a5cd05

Browse files
Implement int_format_into feature
1 parent 22be76b commit 1a5cd05

File tree

3 files changed

+275
-29
lines changed

3 files changed

+275
-29
lines changed

library/core/src/fmt/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ mod float;
1515
#[cfg(no_fp_fmt_parse)]
1616
mod nofloat;
1717
mod num;
18+
mod num_buffer;
1819
mod rt;
1920

2021
#[stable(feature = "fmt_flags_align", since = "1.28.0")]
@@ -33,6 +34,9 @@ pub enum Alignment {
3334
Center,
3435
}
3536

37+
#[unstable(feature = "int_format_into", issue = "138215")]
38+
pub use num_buffer::{NumBuffer, NumBufferTrait};
39+
3640
#[stable(feature = "debug_builders", since = "1.2.0")]
3741
pub use self::builders::{DebugList, DebugMap, DebugSet, DebugStruct, DebugTuple};
3842
#[unstable(feature = "debug_closure_helpers", issue = "117729")]

library/core/src/fmt/num.rs

Lines changed: 212 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Integer and floating-point number formatting
22
3+
use crate::fmt::NumBuffer;
34
use crate::mem::MaybeUninit;
45
use crate::num::fmt as numfmt;
56
use crate::ops::{Div, Rem, Sub};
@@ -199,6 +200,19 @@ static DEC_DIGITS_LUT: &[u8; 200] = b"\
199200
6061626364656667686970717273747576777879\
200201
8081828384858687888990919293949596979899";
201202

203+
/// This function converts a slice of ascii characters into a `&str` starting from `offset`.
204+
///
205+
/// # Safety
206+
///
207+
/// `buf` content starting from `offset` index MUST BE initialized and MUST BE ascii
208+
/// characters.
209+
unsafe fn slice_buffer_to_str(buf: &[MaybeUninit<u8>], offset: usize) -> &str {
210+
// SAFETY: All buf content since offset is set.
211+
let written = unsafe { buf.get_unchecked(offset..) };
212+
// SAFETY: Writes use ASCII from the lookup table exclusively.
213+
unsafe { str::from_utf8_unchecked(written.assume_init_ref()) }
214+
}
215+
202216
macro_rules! impl_Display {
203217
($($signed:ident, $unsigned:ident,)* ; as $u:ident via $conv_fn:ident named $gen_name:ident) => {
204218

@@ -248,6 +262,13 @@ macro_rules! impl_Display {
248262
issue = "none"
249263
)]
250264
pub fn _fmt<'a>(self, buf: &'a mut [MaybeUninit::<u8>]) -> &'a str {
265+
// SAFETY: `buf` will always be big enough to contain all digits.
266+
let offset = unsafe { self._fmt_inner(buf) };
267+
// SAFETY: Starting from `offset`, all elements of the slice have been set.
268+
unsafe { slice_buffer_to_str(buf, offset) }
269+
}
270+
271+
unsafe fn _fmt_inner(self, buf: &mut [MaybeUninit::<u8>]) -> usize {
251272
// Count the number of bytes in buf that are not initialized.
252273
let mut offset = buf.len();
253274
// Consume the least-significant decimals from a working copy.
@@ -309,24 +330,99 @@ macro_rules! impl_Display {
309330
// not used: remain = 0;
310331
}
311332

312-
// SAFETY: All buf content since offset is set.
313-
let written = unsafe { buf.get_unchecked(offset..) };
314-
// SAFETY: Writes use ASCII from the lookup table exclusively.
333+
offset
334+
}
335+
}
336+
337+
impl $signed {
338+
/// Allows users to write an integer (in signed decimal format) into a variable `buf` of
339+
/// type [`NumBuffer`] that is passed by the caller by mutable reference.
340+
///
341+
/// # Examples
342+
///
343+
/// ```
344+
/// #![feature(int_format_into)]
345+
/// use core::fmt::NumBuffer;
346+
///
347+
#[doc = concat!("let n = 0", stringify!($signed), ";")]
348+
/// let mut buf = NumBuffer::new();
349+
/// assert_eq!(n.format_into(&mut buf), "0");
350+
///
351+
#[doc = concat!("let n1 = 32", stringify!($signed), ";")]
352+
/// assert_eq!(n1.format_into(&mut buf), "32");
353+
///
354+
#[doc = concat!("let n2 = ", stringify!($signed::MAX), ";")]
355+
#[doc = concat!("assert_eq!(n2.format_into(&mut buf), ", stringify!($signed::MAX), ".to_string());")]
356+
/// ```
357+
#[unstable(feature = "int_format_into", issue = "138215")]
358+
pub fn format_into(self, buf: &mut NumBuffer<Self>) -> &str {
359+
let mut offset;
360+
361+
#[cfg(not(feature = "optimize_for_size"))]
315362
unsafe {
316-
str::from_utf8_unchecked(slice::from_raw_parts(
317-
MaybeUninit::slice_as_ptr(written),
318-
written.len(),
319-
))
363+
// SAFETY: `buf` will always be big enough to contain all digits.
364+
offset = self.unsigned_abs()._fmt_inner(&mut buf.buf);
365+
}
366+
#[cfg(feature = "optimize_for_size")]
367+
{
368+
offset = _inner_slow_integer_to_str(self.unsigned_abs().$conv_fn(), &mut buf.buf);
369+
}
370+
// Only difference between signed and unsigned are these 4 lines.
371+
if self < 0 {
372+
offset -= 1;
373+
// SAFETY: `buf` will always be big enough for negative numbers to contain all
374+
// digits plus the minus sign.
375+
unsafe { buf.buf.get_unchecked_mut(offset).write(b'-'); }
320376
}
377+
// SAFETY: Starting from `offset`, all elements of the slice have been set.
378+
unsafe { slice_buffer_to_str(&buf.buf, offset) }
321379
}
322-
})*
380+
}
381+
382+
impl $unsigned {
383+
/// Allows users to write an integer (in signed decimal format) into a variable `buf` of
384+
/// type [`NumBuffer`] that is passed by the caller by mutable reference.
385+
///
386+
/// # Examples
387+
///
388+
/// ```
389+
/// #![feature(int_format_into)]
390+
/// use core::fmt::NumBuffer;
391+
///
392+
#[doc = concat!("let n = 0", stringify!($unsigned), ";")]
393+
/// let mut buf = NumBuffer::new();
394+
/// assert_eq!(n.format_into(&mut buf), "0");
395+
///
396+
#[doc = concat!("let n1 = 32", stringify!($unsigned), ";")]
397+
/// assert_eq!(n1.format_into(&mut buf), "32");
398+
///
399+
#[doc = concat!("let n2 = ", stringify!($unsigned::MAX), ";")]
400+
#[doc = concat!("assert_eq!(n2.format_into(&mut buf), ", stringify!($unsigned::MAX), ".to_string());")]
401+
/// ```
402+
#[unstable(feature = "int_format_into", issue = "138215")]
403+
pub fn format_into(self, buf: &mut NumBuffer<Self>) -> &str {
404+
let offset;
405+
406+
#[cfg(not(feature = "optimize_for_size"))]
407+
unsafe {
408+
// SAFETY: `buf` will always be big enough to contain all digits.
409+
offset = self._fmt_inner(&mut buf.buf);
410+
}
411+
#[cfg(feature = "optimize_for_size")]
412+
{
413+
offset = _inner_slow_integer_to_str(self.$conv_fn(), &mut buf.buf);
414+
}
415+
// SAFETY: Starting from `offset`, all elements of the slice have been set.
416+
unsafe { slice_buffer_to_str(&buf.buf, offset) }
417+
}
418+
}
419+
420+
421+
)*
323422

324423
#[cfg(feature = "optimize_for_size")]
325-
fn $gen_name(mut n: $u, is_nonnegative: bool, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326-
const MAX_DEC_N: usize = $u::MAX.ilog10() as usize + 1;
327-
let mut buf = [MaybeUninit::<u8>::uninit(); MAX_DEC_N];
328-
let mut curr = MAX_DEC_N;
329-
let buf_ptr = MaybeUninit::slice_as_mut_ptr(&mut buf);
424+
fn _inner_slow_integer_to_str(mut n: $u, buf: &mut [MaybeUninit::<u8>]) -> usize {
425+
let mut curr = buf.len();
330426

331427
// SAFETY: To show that it's OK to copy into `buf_ptr`, notice that at the beginning
332428
// `curr == buf.len() == 39 > log(n)` since `n < 2^128 < 10^39`, and at
@@ -336,20 +432,26 @@ macro_rules! impl_Display {
336432
unsafe {
337433
loop {
338434
curr -= 1;
339-
buf_ptr.add(curr).write((n % 10) as u8 + b'0');
435+
// SAFETY: `buf` will always be big enough to contain all digits.
436+
buf.get_unchecked(curr).write((n % 10) as u8 + b'0');
340437
n /= 10;
341438

342439
if n == 0 {
343440
break;
344441
}
345442
}
346443
}
444+
cur
445+
}
347446

348-
// SAFETY: `curr` > 0 (since we made `buf` large enough), and all the chars are valid UTF-8
349-
let buf_slice = unsafe {
350-
str::from_utf8_unchecked(
351-
slice::from_raw_parts(buf_ptr.add(curr), buf.len() - curr))
352-
};
447+
#[cfg(feature = "optimize_for_size")]
448+
fn $gen_name(n: $u, is_nonnegative: bool, f: &mut fmt::Formatter<'_>) -> fmt::Result {
449+
const MAX_DEC_N: usize = $u::MAX.ilog(10) as usize + 1;
450+
let mut buf = [MaybeUninit::<u8>::uninit(); MAX_DEC_N];
451+
452+
let offset = _inner_slow_integer_to_str(n, &mut buf);
453+
// SAFETY: Starting from `offset`, all elements of the slice have been set.
454+
let buf_slice = unsafe { slice_buffer_to_str(&buf, offset) };
353455
f.pad_integral(is_nonnegative, "", buf_slice)
354456
}
355457
};
@@ -598,12 +700,23 @@ impl u128 {
598700
issue = "none"
599701
)]
600702
pub fn _fmt<'a>(self, buf: &'a mut [MaybeUninit<u8>]) -> &'a str {
703+
// SAFETY: `buf` will always be big enough to contain all digits.
704+
let offset = unsafe { self._fmt_inner(buf) };
705+
// SAFETY: Starting from `offset`, all elements of the slice have been set.
706+
unsafe { slice_buffer_to_str(buf, offset) }
707+
}
708+
709+
unsafe fn _fmt_inner(self, buf: &mut [MaybeUninit<u8>]) -> usize {
601710
// Optimize common-case zero, which would also need special treatment due to
602711
// its "leading" zero.
603712
if self == 0 {
604-
return "0";
713+
let offset = buf.len() - 1;
714+
// SAFETY: `buf` will always be big enough to contain all digits.
715+
unsafe {
716+
buf.get_unchecked_mut(offset).write(b'0');
717+
}
718+
return offset;
605719
}
606-
607720
// Take the 16 least-significant decimals.
608721
let (quot_1e16, mod_1e16) = div_rem_1e16(self);
609722
let (mut remain, mut offset) = if quot_1e16 == 0 {
@@ -677,16 +790,86 @@ impl u128 {
677790
buf[offset].write(DEC_DIGITS_LUT[last * 2 + 1]);
678791
// not used: remain = 0;
679792
}
793+
offset
794+
}
680795

681-
// SAFETY: All buf content since offset is set.
682-
let written = unsafe { buf.get_unchecked(offset..) };
683-
// SAFETY: Writes use ASCII from the lookup table exclusively.
684-
unsafe {
685-
str::from_utf8_unchecked(slice::from_raw_parts(
686-
MaybeUninit::slice_as_ptr(written),
687-
written.len(),
688-
))
796+
/// Allows users to write an integer (in signed decimal format) into a variable `buf` of
797+
/// type [`NumBuffer`] that is passed by the caller by mutable reference.
798+
///
799+
/// # Examples
800+
///
801+
/// ```
802+
/// #![feature(int_format_into)]
803+
/// use core::fmt::NumBuffer;
804+
///
805+
/// let n = 0u128;
806+
/// let mut buf = NumBuffer::new();
807+
/// assert_eq!(n.format_into(&mut buf), "0");
808+
///
809+
/// let n1 = 32u128;
810+
/// let mut buf1 = NumBuffer::new();
811+
/// assert_eq!(n1.format_into(&mut buf1), "32");
812+
///
813+
/// let n2 = u128::MAX;
814+
/// let mut buf2 = NumBuffer::new();
815+
/// assert_eq!(n2.format_into(&mut buf2), u128::MAX.to_string());
816+
/// ```
817+
#[unstable(feature = "int_format_into", issue = "138215")]
818+
pub fn format_into(self, buf: &mut NumBuffer<Self>) -> &str {
819+
let diff = buf.capacity() - U128_MAX_DEC_N;
820+
// FIXME: Once const generics are better, use `NumberBufferTrait::BUF_SIZE` as generic const
821+
// for `fmt_u128_inner`.
822+
//
823+
// In the meantime, we have to use a slice starting at index 1 and add 1 to the returned
824+
// offset to ensure the number is correctly generated at the end of the buffer.
825+
// SAFETY: `diff` will always be between 0 and its initial value.
826+
unsafe { self._fmt(buf.buf.get_unchecked_mut(diff..)) }
827+
}
828+
}
829+
830+
impl i128 {
831+
/// Allows users to write an integer (in signed decimal format) into a variable `buf` of
832+
/// type [`NumBuffer`] that is passed by the caller by mutable reference.
833+
///
834+
/// # Examples
835+
///
836+
/// ```
837+
/// #![feature(int_format_into)]
838+
/// use core::fmt::NumBuffer;
839+
///
840+
/// let n = 0i128;
841+
/// let mut buf = NumBuffer::new();
842+
/// assert_eq!(n.format_into(&mut buf), "0");
843+
///
844+
/// let n1 = i128::MIN;
845+
/// assert_eq!(n1.format_into(&mut buf), i128::MIN.to_string());
846+
///
847+
/// let n2 = i128::MAX;
848+
/// assert_eq!(n2.format_into(&mut buf), i128::MAX.to_string());
849+
/// ```
850+
#[unstable(feature = "int_format_into", issue = "138215")]
851+
pub fn format_into(self, buf: &mut NumBuffer<Self>) -> &str {
852+
let diff = buf.capacity() - U128_MAX_DEC_N;
853+
// FIXME: Once const generics are better, use `NumberBufferTrait::BUF_SIZE` as generic const
854+
// for `fmt_u128_inner`.
855+
//
856+
// In the meantime, we have to use a slice starting at index 1 and add 1 to the returned
857+
// offset to ensure the number is correctly generated at the end of the buffer.
858+
// SAFETY: `buf` will always be big enough to contain all digits.
859+
let mut offset =
860+
unsafe { self.unsigned_abs()._fmt_inner(buf.buf.get_unchecked_mut(diff..)) };
861+
// We put back the offset at the right position.
862+
offset += diff;
863+
// Only difference between signed and unsigned are these 4 lines.
864+
if self < 0 {
865+
offset -= 1;
866+
// SAFETY: `buf` will always be big enough to contain all digits plus the minus sign.
867+
unsafe {
868+
buf.buf.get_unchecked_mut(offset).write(b'-');
869+
}
689870
}
871+
// SAFETY: Starting from `offset`, all elements of the slice have been set.
872+
unsafe { slice_buffer_to_str(&buf.buf, offset) }
690873
}
691874
}
692875

library/core/src/fmt/num_buffer.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use crate::mem::MaybeUninit;
2+
3+
/// Trait used to describe the maximum number of digits in decimal base of the implemented integer.
4+
#[unstable(feature = "int_format_into", issue = "138215")]
5+
pub trait NumBufferTrait {
6+
/// Maximum number of digits in decimal base of the implemented integer.
7+
const BUF_SIZE: usize;
8+
}
9+
10+
macro_rules! impl_NumBufferTrait {
11+
($($signed:ident, $unsigned:ident,)*) => {
12+
$(
13+
#[unstable(feature = "int_format_into", issue = "138215")]
14+
impl NumBufferTrait for $signed {
15+
const BUF_SIZE: usize = $signed::MIN.ilog(10) as usize + 1;
16+
}
17+
#[unstable(feature = "int_format_into", issue = "138215")]
18+
impl NumBufferTrait for $unsigned {
19+
const BUF_SIZE: usize = $unsigned::MAX.ilog(10) as usize + 1;
20+
}
21+
)*
22+
}
23+
}
24+
25+
impl_NumBufferTrait! {
26+
i8, u8,
27+
i16, u16,
28+
i32, u32,
29+
i64, u64,
30+
isize, usize,
31+
i128, u128,
32+
}
33+
34+
/// A buffer wrapper of which the internal size is based on the maximum
35+
/// number of digits the associated integer can have.
36+
#[unstable(feature = "int_format_into", issue = "138215")]
37+
#[derive(Debug)]
38+
pub struct NumBuffer<T: NumBufferTrait> {
39+
// FIXME: Once const generics feature is working, use `T::BUF_SIZE` instead of 40.
40+
pub(crate) buf: [MaybeUninit<u8>; 40],
41+
// FIXME: Remove this field once we can actually use `T`.
42+
phantom: core::marker::PhantomData<T>,
43+
}
44+
45+
#[unstable(feature = "int_format_into", issue = "138215")]
46+
impl<T: NumBufferTrait> NumBuffer<T> {
47+
/// Initializes internal buffer.
48+
#[unstable(feature = "int_format_into", issue = "138215")]
49+
pub const fn new() -> Self {
50+
// FIXME: Once const generics feature is working, use `T::BUF_SIZE` instead of 40.
51+
NumBuffer { buf: [MaybeUninit::<u8>::uninit(); 40], phantom: core::marker::PhantomData }
52+
}
53+
54+
/// Returns the length of the internal buffer.
55+
#[unstable(feature = "int_format_into", issue = "138215")]
56+
pub const fn capacity(&self) -> usize {
57+
self.buf.len()
58+
}
59+
}

0 commit comments

Comments
 (0)