Skip to content

Commit 82c6037

Browse files
Implement int_format_into feature
1 parent 6268d0a commit 82c6037

File tree

4 files changed

+290
-44
lines changed

4 files changed

+290
-44
lines changed

library/alloc/src/string.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2875,7 +2875,8 @@ macro_rules! impl_to_string {
28752875
out = String::with_capacity(SIZE);
28762876
}
28772877

2878-
out.push_str(self.unsigned_abs()._fmt(&mut buf));
2878+
// SAFETY: `buf` is always big enough to contain all the digits.
2879+
unsafe { out.push_str(self.unsigned_abs()._fmt(&mut buf)); }
28792880
out
28802881
}
28812882
}
@@ -2887,7 +2888,8 @@ macro_rules! impl_to_string {
28872888
const SIZE: usize = $unsigned::MAX.ilog10() as usize + 1;
28882889
let mut buf = [core::mem::MaybeUninit::<u8>::uninit(); SIZE];
28892890

2890-
self._fmt(&mut buf).to_string()
2891+
// SAFETY: `buf` is always big enough to contain all the digits.
2892+
unsafe { self._fmt(&mut buf).to_string() }
28912893
}
28922894
}
28932895
)*

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: 222 additions & 42 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,20 @@ 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: `offset` is always included between 0 and `buf`'s length.
211+
let written = unsafe { buf.get_unchecked(offset..) };
212+
// SAFETY: (`assume_init_ref`) All buf content since offset is set.
213+
// SAFETY: (`from_utf8_unchecked`) Writes use ASCII from the lookup table exclusively.
214+
unsafe { str::from_utf8_unchecked(written.assume_init_ref()) }
215+
}
216+
202217
macro_rules! impl_Display {
203218
($($signed:ident, $unsigned:ident,)* ; as $u:ident via $conv_fn:ident named $gen_name:ident) => {
204219

@@ -212,7 +227,8 @@ macro_rules! impl_Display {
212227
// Buffer decimals for $unsigned with right alignment.
213228
let mut buf = [MaybeUninit::<u8>::uninit(); MAX_DEC_N];
214229

215-
f.pad_integral(true, "", self._fmt(&mut buf))
230+
// SAFETY: `buf` is always big enough to contain all the digits.
231+
unsafe { f.pad_integral(true, "", self._fmt(&mut buf)) }
216232
}
217233
#[cfg(feature = "optimize_for_size")]
218234
{
@@ -230,7 +246,8 @@ macro_rules! impl_Display {
230246
// Buffer decimals for $unsigned with right alignment.
231247
let mut buf = [MaybeUninit::<u8>::uninit(); MAX_DEC_N];
232248

233-
f.pad_integral(*self >= 0, "", self.unsigned_abs()._fmt(&mut buf))
249+
// SAFETY: `buf` is always big enough to contain all the digits.
250+
unsafe { f.pad_integral(*self >= 0, "", self.unsigned_abs()._fmt(&mut buf)) }
234251
}
235252
#[cfg(feature = "optimize_for_size")]
236253
{
@@ -247,7 +264,14 @@ macro_rules! impl_Display {
247264
reason = "specialized method meant to only be used by `SpecToString` implementation",
248265
issue = "none"
249266
)]
250-
pub fn _fmt<'a>(self, buf: &'a mut [MaybeUninit::<u8>]) -> &'a str {
267+
pub unsafe fn _fmt<'a>(self, buf: &'a mut [MaybeUninit::<u8>]) -> &'a str {
268+
// SAFETY: `buf` will always be big enough to contain all digits.
269+
let offset = unsafe { self._fmt_inner(buf) };
270+
// SAFETY: Starting from `offset`, all elements of the slice have been set.
271+
unsafe { slice_buffer_to_str(buf, offset) }
272+
}
273+
274+
unsafe fn _fmt_inner(self, buf: &mut [MaybeUninit::<u8>]) -> usize {
251275
// Count the number of bytes in buf that are not initialized.
252276
let mut offset = buf.len();
253277
// Consume the least-significant decimals from a working copy.
@@ -309,47 +333,123 @@ macro_rules! impl_Display {
309333
// not used: remain = 0;
310334
}
311335

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

324424
#[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);
425+
fn _inner_slow_integer_to_str(mut n: $u, buf: &mut [MaybeUninit::<u8>]) -> usize {
426+
let mut curr = buf.len();
330427

331428
// SAFETY: To show that it's OK to copy into `buf_ptr`, notice that at the beginning
332429
// `curr == buf.len() == 39 > log(n)` since `n < 2^128 < 10^39`, and at
333430
// each step this is kept the same as `n` is divided. Since `n` is always
334431
// non-negative, this means that `curr > 0` so `buf_ptr[curr..curr + 1]`
335432
// is safe to access.
336-
unsafe {
337-
loop {
338-
curr -= 1;
339-
buf_ptr.add(curr).write((n % 10) as u8 + b'0');
340-
n /= 10;
433+
loop {
434+
curr -= 1;
435+
buf[curr].write((n % 10) as u8 + b'0');
436+
n /= 10;
341437

342-
if n == 0 {
343-
break;
344-
}
438+
if n == 0 {
439+
break;
345440
}
346441
}
442+
curr
443+
}
347444

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-
};
445+
#[cfg(feature = "optimize_for_size")]
446+
fn $gen_name(n: $u, is_nonnegative: bool, f: &mut fmt::Formatter<'_>) -> fmt::Result {
447+
const MAX_DEC_N: usize = $u::MAX.ilog(10) as usize + 1;
448+
let mut buf = [MaybeUninit::<u8>::uninit(); MAX_DEC_N];
449+
450+
let offset = _inner_slow_integer_to_str(n, &mut buf);
451+
// SAFETY: Starting from `offset`, all elements of the slice have been set.
452+
let buf_slice = unsafe { slice_buffer_to_str(&buf, offset) };
353453
f.pad_integral(is_nonnegative, "", buf_slice)
354454
}
355455
};
@@ -572,7 +672,8 @@ impl fmt::Display for u128 {
572672
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
573673
let mut buf = [MaybeUninit::<u8>::uninit(); U128_MAX_DEC_N];
574674

575-
f.pad_integral(true, "", self._fmt(&mut buf))
675+
// SAFETY: `buf` is always big enough to contain all the digits.
676+
unsafe { f.pad_integral(true, "", self._fmt(&mut buf)) }
576677
}
577678
}
578679

@@ -584,7 +685,8 @@ impl fmt::Display for i128 {
584685
let mut buf = [MaybeUninit::<u8>::uninit(); U128_MAX_DEC_N];
585686

586687
let is_nonnegative = *self >= 0;
587-
f.pad_integral(is_nonnegative, "", self.unsigned_abs()._fmt(&mut buf))
688+
// SAFETY: `buf` is always big enough to contain all the digits.
689+
unsafe { f.pad_integral(is_nonnegative, "", self.unsigned_abs()._fmt(&mut buf)) }
588690
}
589691
}
590692

@@ -597,13 +699,21 @@ impl u128 {
597699
reason = "specialized method meant to only be used by `SpecToString` implementation",
598700
issue = "none"
599701
)]
600-
pub fn _fmt<'a>(self, buf: &'a mut [MaybeUninit<u8>]) -> &'a str {
702+
pub unsafe 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+
buf[offset].write(b'0');
715+
return offset;
605716
}
606-
607717
// Take the 16 least-significant decimals.
608718
let (quot_1e16, mod_1e16) = div_rem_1e16(self);
609719
let (mut remain, mut offset) = if quot_1e16 == 0 {
@@ -677,16 +787,86 @@ impl u128 {
677787
buf[offset].write(DEC_DIGITS_LUT[last * 2 + 1]);
678788
// not used: remain = 0;
679789
}
790+
offset
791+
}
680792

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

0 commit comments

Comments
 (0)