diff --git a/src/lib.rs b/src/lib.rs index cba1706..b1ae4b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,7 +75,7 @@ #![no_std] use core::fmt::{Debug, Display, Formatter}; -use core::mem::size_of; +use core::mem::{size_of, MaybeUninit}; use core::ops::Deref; use core::str; @@ -111,6 +111,10 @@ pub trait NumToA { /// ``` fn numtoa(self, base: Self, string: &mut [u8]) -> &[u8]; + fn numtoa_uninit(self, base: Self, string: &mut [MaybeUninit]) -> &[u8]; + + fn numtoa_uninit_str(self, base: Self, string: &mut [MaybeUninit]) -> &str; + /// Convenience method for quickly getting a string from the input's array buffer. fn numtoa_str(self, base: Self, buf: &mut [u8]) -> &str; } @@ -130,22 +134,33 @@ const DEC_LOOKUP: &[u8; 200] = b"0001020304050607080910111213141516171819\ const MAX_SUPPORTED_BASE: u128 = LOOKUP.len() as u128; /// The result of a number conversion to ascii containing a string with at most length `N` bytes/characters +// invariant: string[start..] always contains valid ascii pub struct AsciiNumber { - string: [u8; N], + string: [MaybeUninit; N], start: usize, } impl AsciiNumber { + + const fn new(string: [MaybeUninit; N], start: usize) -> Self { + if cfg!(debug_assertions) { + debug_assert!(core::str::from_utf8(unsafe { assume_slice_init(string.split_at(start).1) }).is_ok(), "ascii number created with non-ascii data"); + } + Self { string, start } + } + /// Get the ascii representation of the number as a byte slice pub const fn as_slice(&self) -> &[u8] { - self.string.split_at(self.start).1 + // SAFETY: invariant + unsafe { assume_slice_init(self.string.split_at(self.start).1) } } /// Get the ascii representation of the number as a string slice pub const fn as_str(&self) -> &str { + // SAFETY: invariant unsafe { core::str::from_utf8_unchecked(Self::as_slice(self)) } } /// Consume this AsciiNumber to return the underlying buffer & string start position - pub const fn into_inner(self) -> ([u8;N], usize) { + pub const fn into_inner(self) -> ([MaybeUninit;N], usize) { (self.string, self.start) } } @@ -169,10 +184,19 @@ impl Debug for AsciiNumber { } } +const fn assume_mut_slice_uninit(thing: &mut [u8]) -> &mut [MaybeUninit] { + // SAFETY: tbh i think this is always safe + unsafe { core::mem::transmute::<&mut [u8], &mut [MaybeUninit]>(thing) } +} + +const unsafe fn assume_slice_init(thing: &[MaybeUninit]) -> &[u8] { + core::mem::transmute::<&[MaybeUninit], &[u8]>(thing) +} + macro_rules! copy_2_dec_lut_bytes { ($to:ident,$to_index:expr,$lut_index:expr) => { - $to[$to_index as usize] = DEC_LOOKUP[$lut_index as usize]; - $to[$to_index as usize+1] = DEC_LOOKUP[$lut_index as usize+1]; + $to[$to_index as usize] = MaybeUninit::new(DEC_LOOKUP[$lut_index as usize]); + $to[$to_index as usize+1] = MaybeUninit::new(DEC_LOOKUP[$lut_index as usize+1]); }; } @@ -195,14 +219,14 @@ macro_rules! base_10 { } else if $number > 99 { let section = ($number as u16 / 10) * 2; copy_2_dec_lut_bytes!($string, $index-2, section); - $string[$index] = LOOKUP[($number % 10) as usize]; + $string[$index] = MaybeUninit::new(LOOKUP[($number % 10) as usize]); $index = $index.wrapping_sub(3); } else if $number > 9 { $number *= 2; copy_2_dec_lut_bytes!($string, $index-1, $number); $index = $index.wrapping_sub(2); } else { - $string[$index] = LOOKUP[$number as usize]; + $string[$index] = MaybeUninit::new(LOOKUP[$number as usize]); $index = $index.wrapping_sub(1); } } @@ -211,11 +235,13 @@ macro_rules! base_10 { macro_rules! impl_unsigned_numtoa_for { ( $type_name:ty, + $uninit_core_function_name:ident, + $uninit_str_function_name:ident, $core_function_name:ident, $str_function_name:ident ) => { - pub const fn $core_function_name(mut num: $type_name, base: $type_name, string: &mut [u8]) -> &[u8] { + pub const fn $uninit_core_function_name(mut num: $type_name, base: $type_name, string: &mut [MaybeUninit]) -> &[u8] { // Check if the buffer is large enough and panic on debug builds if it isn't if cfg!(debug_assertions) { debug_assert!(base > 1 && base as u128 <= MAX_SUPPORTED_BASE, "unsupported base"); @@ -232,8 +258,8 @@ macro_rules! impl_unsigned_numtoa_for { let mut index = string.len() - 1; if num == 0 { - string[index] = b'0'; - return string.split_at(index).1; + string[index] = MaybeUninit::new(b'0'); + return unsafe { assume_slice_init(string.split_at(index).1) } } if base == 10 { @@ -242,13 +268,21 @@ macro_rules! impl_unsigned_numtoa_for { } else { while num != 0 { let rem = num % base; - string[index] = LOOKUP[rem as usize]; + string[index] = MaybeUninit::new(LOOKUP[rem as usize]); index = index.wrapping_sub(1); num /= base; } } - string.split_at(index.wrapping_add(1)).1 + unsafe { assume_slice_init(string.split_at(index.wrapping_add(1)).1) } + } + + pub const fn $uninit_str_function_name(num: $type_name, base: $type_name, string: &mut [MaybeUninit]) -> &str { + unsafe { core::str::from_utf8_unchecked($uninit_core_function_name(num, base, string)) } + } + + pub const fn $core_function_name(num: $type_name, base: $type_name, string: &mut [u8]) -> &[u8] { + $uninit_core_function_name(num, base, assume_mut_slice_uninit(string)) } pub const fn $str_function_name(num: $type_name, base: $type_name, string: &mut [u8]) -> &str { @@ -256,8 +290,14 @@ macro_rules! impl_unsigned_numtoa_for { } impl NumToA for $type_name { + fn numtoa_uninit(self, base: $type_name, string: &mut [MaybeUninit]) -> &[u8] { + $uninit_core_function_name(self, base, string) + } + fn numtoa_uninit_str(self, base: $type_name, string: &mut [MaybeUninit]) -> &str { + $uninit_str_function_name(self, base, string) + } fn numtoa(self, base: $type_name, string: &mut [u8]) -> &[u8] { - $core_function_name(self, base, string) + $core_function_name(self, base, string) } fn numtoa_str(self, base: $type_name, buf: &mut [u8]) -> &str { $str_function_name(self, base, buf) @@ -270,11 +310,13 @@ macro_rules! impl_unsigned_numtoa_for { macro_rules! impl_signed_numtoa_for { ( $type_name:ty, + $uninit_function_name:ident, + $uninit_str_function_name:ident, $core_function_name:ident, $str_function_name:ident ) => { - pub const fn $core_function_name(mut num: $type_name, base: $type_name, string: &mut [u8]) -> &[u8] { + pub const fn $uninit_function_name(mut num: $type_name, base: $type_name, string: &mut [MaybeUninit]) -> &[u8] { if cfg!(debug_assertions) { debug_assert!(base > 1 && base as u128 <= MAX_SUPPORTED_BASE, "unsupported base"); if base == 10 { @@ -297,14 +339,14 @@ macro_rules! impl_signed_numtoa_for { Some(value) => value, None => { let value = <$type_name>::max_value(); - string[index] = LOOKUP[((value % base + 1) % base) as usize]; + string[index] = MaybeUninit::new(LOOKUP[((value % base + 1) % base) as usize]); index -= 1; value / base + ((value % base == base - 1) as $type_name) } }; } else if num == 0 { - string[index] = b'0'; - return string.split_at(index).1; + string[index] = MaybeUninit::new(b'0'); + return unsafe { assume_slice_init(string.split_at(index).1) }; } if base == 10 { @@ -313,18 +355,26 @@ macro_rules! impl_signed_numtoa_for { } else { while num != 0 { let rem = num % base; - string[index] = LOOKUP[rem as usize]; + string[index] = MaybeUninit::new(LOOKUP[rem as usize]); index = index.wrapping_sub(1); num /= base; } } if is_negative { - string[index] = b'-'; + string[index] = MaybeUninit::new(b'-'); index = index.wrapping_sub(1); } - string.split_at(index.wrapping_add(1)).1 + unsafe { assume_slice_init(string.split_at(index.wrapping_add(1)).1) } + } + + pub const fn $uninit_str_function_name(num: $type_name, base: $type_name, string: &mut [MaybeUninit]) -> &str { + unsafe { core::str::from_utf8_unchecked($uninit_function_name(num, base, string)) } + } + + pub const fn $core_function_name(num: $type_name, base: $type_name, string: &mut [u8]) -> &[u8] { + $uninit_function_name(num, base, assume_mut_slice_uninit(string)) } pub const fn $str_function_name(num: $type_name, base: $type_name, string: &mut [u8]) -> &str { @@ -332,10 +382,15 @@ macro_rules! impl_signed_numtoa_for { } impl NumToA for $type_name { + fn numtoa_uninit(self, base: $type_name, string: &mut [MaybeUninit]) -> &[u8] { + $uninit_function_name(self, base, string) + } + fn numtoa_uninit_str(self, base: $type_name, string: &mut [MaybeUninit]) -> &str { + $uninit_str_function_name(self, base, string) + } fn numtoa(self, base: $type_name, string: &mut [u8]) -> &[u8] { $core_function_name(self, base, string) } - fn numtoa_str(self, base: $type_name, buf: &mut [u8]) -> &str { $str_function_name(self, base, buf) } @@ -343,18 +398,18 @@ macro_rules! impl_signed_numtoa_for { } } -impl_signed_numtoa_for!(i16,numtoa_i16,numtoa_i16_str); -impl_signed_numtoa_for!(i32,numtoa_i32,numtoa_i32_str); -impl_signed_numtoa_for!(i64,numtoa_i64,numtoa_i64_str); -impl_signed_numtoa_for!(i128,numtoa_i128,numtoa_i128_str); -impl_signed_numtoa_for!(isize,numtoa_isize,numtoa_isize_str); -impl_unsigned_numtoa_for!(u16,numtoa_u16,numtoa_u16_str); -impl_unsigned_numtoa_for!(u32,numtoa_u32,numtoa_u32_str); -impl_unsigned_numtoa_for!(u64,numtoa_u64,numtoa_u64_str); -impl_unsigned_numtoa_for!(u128,numtoa_u128,numtoa_u128_str); -impl_unsigned_numtoa_for!(usize,numtoa_usize,numtoa_usize_str); +impl_signed_numtoa_for!(i16,numtoa_uninit_i16,numtoa_uninit_i16_str,numtoa_i16,numtoa_i16_str); +impl_signed_numtoa_for!(i32,numtoa_uninit_i32,numtoa_uninit_i32_str,numtoa_i32,numtoa_i32_str); +impl_signed_numtoa_for!(i64,numtoa_uninit_i64,numtoa_uninit_i64_str,numtoa_i64,numtoa_i64_str); +impl_signed_numtoa_for!(i128,numtoa_uninit_i128,numtoa_uninit_i128_str,numtoa_i128,numtoa_i128_str); +impl_signed_numtoa_for!(isize,numtoa_uninit_isize,numtoa_uninit_isize_str,numtoa_isize,numtoa_isize_str); +impl_unsigned_numtoa_for!(u16,numtoa_uninit_u16,numtoa_uninit_u16_str,numtoa_u16,numtoa_u16_str); +impl_unsigned_numtoa_for!(u32,numtoa_uninit_u32,numtoa_uninit_u32_str,numtoa_u32,numtoa_u32_str); +impl_unsigned_numtoa_for!(u64,numtoa_uninit_u64,numtoa_uninit_u64_str,numtoa_u64,numtoa_u64_str); +impl_unsigned_numtoa_for!(u128,numtoa_uninit_u128,numtoa_uninit_u128_str,numtoa_u128,numtoa_u128_str); +impl_unsigned_numtoa_for!(usize,numtoa_uninit_usize,numtoa_uninit_usize_str,numtoa_usize,numtoa_usize_str); -pub const fn numtoa_i8(mut num: i8, base: i8, string: &mut [u8]) -> &[u8] { +pub const fn numtoa_uninit_i8(mut num: i8, base: i8, string: &mut [MaybeUninit]) -> &[u8] { if cfg!(debug_assertions) { debug_assert!(base > 1 && base as u128 <= MAX_SUPPORTED_BASE, "unsupported base"); if base == 10 { @@ -371,45 +426,53 @@ pub const fn numtoa_i8(mut num: i8, base: i8, string: &mut [u8]) -> &[u8] { Some(value) => value, None => { let value = ::max_value(); - string[index] = LOOKUP[((value % base + 1) % base) as usize]; + string[index] = MaybeUninit::new(LOOKUP[((value % base + 1) % base) as usize]); index -= 1; value / base + ((value % base == base - 1) as i8) } }; } else if num == 0 { - string[index] = b'0'; - return string.split_at(index).1; + string[index] = MaybeUninit::new(b'0'); + return unsafe { assume_slice_init(string.split_at(index).1) }; } if base == 10 { if num > 99 { let section = (num / 10) * 2; copy_2_dec_lut_bytes!(string, index-2, section); - string[index] = LOOKUP[(num % 10) as usize]; + string[index] = MaybeUninit::new(LOOKUP[(num % 10) as usize]); index = index.wrapping_sub(3); } else if num > 9 { let idx = num as usize * 2; copy_2_dec_lut_bytes!(string, index-1, idx); index = index.wrapping_sub(2); } else { - string[index] = LOOKUP[num as usize]; + string[index] = MaybeUninit::new(LOOKUP[num as usize]); index = index.wrapping_sub(1); } } else { while num != 0 { let rem = num % base; - string[index] = LOOKUP[rem as usize]; + string[index] = MaybeUninit::new(LOOKUP[rem as usize]); index = index.wrapping_sub(1); num /= base; } } if is_negative { - string[index] = b'-'; + string[index] = MaybeUninit::new(b'-'); index = index.wrapping_sub(1); } - string.split_at(index.wrapping_add(1)).1 + unsafe { assume_slice_init(string.split_at(index.wrapping_add(1)).1) } +} + +pub const fn numtoa_uninit_i8_str(num: i8, base: i8, string: &mut [MaybeUninit]) -> &str { + unsafe { str::from_utf8_unchecked(numtoa_uninit_i8(num, base, string)) } +} + +pub const fn numtoa_i8(num: i8, base: i8, string: &mut [u8]) -> &[u8] { + numtoa_uninit_i8(num, base, assume_mut_slice_uninit(string)) } pub const fn numtoa_i8_str(num: i8, base: i8, string: &mut [u8]) -> &str { @@ -417,16 +480,21 @@ pub const fn numtoa_i8_str(num: i8, base: i8, string: &mut [u8]) -> &str { } impl NumToA for i8 { + fn numtoa_uninit(self, base: i8, string: &mut [MaybeUninit]) -> &[u8] { + numtoa_uninit_i8(self, base, string) + } + fn numtoa_uninit_str(self, base: i8, string: &mut [MaybeUninit]) -> &str { + numtoa_uninit_i8_str(self, base, string) + } fn numtoa(self, base: i8, string: &mut [u8]) -> &[u8] { numtoa_i8(self, base, string) } - fn numtoa_str(self, base: Self, buf: &mut [u8]) -> &str { numtoa_i8_str(self, base, buf) } } -pub const fn numtoa_u8(mut num: u8, base: u8, string: &mut [u8]) -> &[u8] { +pub const fn numtoa_uninit_u8(mut num: u8, base: u8, string: &mut [MaybeUninit]) -> &[u8] { if cfg!(debug_assertions) { debug_assert!(base > 1 && base as u128 <= MAX_SUPPORTED_BASE, "unsupported base"); if base == 10 { @@ -436,34 +504,42 @@ pub const fn numtoa_u8(mut num: u8, base: u8, string: &mut [u8]) -> &[u8] { let mut index = string.len() - 1; if num == 0 { - string[index] = b'0'; - return string.split_at(index).1; + string[index] = MaybeUninit::new(b'0'); + return unsafe { assume_slice_init(string.split_at(index).1) }; } if base == 10 { if num > 99 { let section = (num / 10) * 2; copy_2_dec_lut_bytes!(string, index-2, section); - string[index] = LOOKUP[(num % 10) as usize]; + string[index] = MaybeUninit::new(LOOKUP[(num % 10) as usize]); index = index.wrapping_sub(3); } else if num > 9 { num *= 2; copy_2_dec_lut_bytes!(string, index-1, num); index = index.wrapping_sub(2); } else { - string[index] = LOOKUP[num as usize]; + string[index] = MaybeUninit::new(LOOKUP[num as usize]); index = index.wrapping_sub(1); } } else { while num != 0 { let rem = num % base; - string[index] = LOOKUP[rem as usize]; + string[index] = MaybeUninit::new(LOOKUP[rem as usize]); index = index.wrapping_sub(1); num /= base; } } - string.split_at(index.wrapping_add(1)).1 + unsafe { assume_slice_init(string.split_at(index.wrapping_add(1)).1) } +} + +pub const fn numtoa_uninit_u8_str(num: u8, base: u8, string: &mut [MaybeUninit]) -> &str { + unsafe { str::from_utf8_unchecked(numtoa_uninit_u8(num, base, string)) } +} + +pub const fn numtoa_u8(num: u8, base: u8, string: &mut [u8]) -> &[u8] { + numtoa_uninit_u8(num, base, assume_mut_slice_uninit(string)) } pub const fn numtoa_u8_str(num: u8, base: u8, string: &mut [u8]) -> &str { @@ -471,10 +547,15 @@ pub const fn numtoa_u8_str(num: u8, base: u8, string: &mut [u8]) -> &str { } impl NumToA for u8 { + fn numtoa_uninit(self, base: u8, string: &mut [MaybeUninit]) -> &[u8] { + numtoa_uninit_u8(self, base, string) + } + fn numtoa_uninit_str(self, base: u8, string: &mut [MaybeUninit]) -> &str { + numtoa_uninit_u8_str(self, base, string) + } fn numtoa(self, base: u8, string: &mut [u8]) -> &[u8] { numtoa_u8(self, base, string) } - fn numtoa_str(self, base: Self, buf: &mut [u8]) -> &str { numtoa_u8_str(self, base, buf) } @@ -490,17 +571,19 @@ macro_rules! impl_numtoa_streamlined_for_type { $needed_buffer_size:expr) => { pub const fn $base_n_function_name(num: $type_name) -> AsciiNumber<$needed_buffer_size> { - let mut string = [0_u8; $needed_buffer_size]; + let mut string: [MaybeUninit; $needed_buffer_size] = [MaybeUninit::uninit(); $needed_buffer_size]; let start = $needed_buffer_size - $core_function_name(num, $base, &mut string).len(); - return AsciiNumber { string, start } + return AsciiNumber::new(string, start) } pub const fn $padded_function_name(num: $type_name, padding: u8) -> AsciiNumber { const { assert!(LENGTH >= $needed_buffer_size) } - let mut string = [padding; LENGTH]; - let _ = $core_function_name(num, $base, &mut string); - return AsciiNumber { string, start: 0 } + let mut string = [MaybeUninit::new(padding); LENGTH]; + let _ = $needed_buffer_size - $core_function_name(num, $base, &mut string).len(); + // n.b. this is safe because any repeating u8 is guaranteed to be ascii + return AsciiNumber::new(string, 0) } + }; } @@ -520,29 +603,30 @@ macro_rules! impl_numtoa_streamlined_for { $i128_needed_size:expr ) => { pub mod $base_module_name { - + + use core::mem::MaybeUninit; use AsciiNumber; - use numtoa_u8; - use numtoa_u16; - use numtoa_u32; - use numtoa_u64; - use numtoa_u128; - use numtoa_i8; - use numtoa_i16; - use numtoa_i32; - use numtoa_i64; - use numtoa_i128; - - impl_numtoa_streamlined_for_type!(u8,$base_value,numtoa_u8,u8,u8_padded,$u8_needed_size); - impl_numtoa_streamlined_for_type!(u16,$base_value,numtoa_u16,u16,u16_padded,$u16_needed_size); - impl_numtoa_streamlined_for_type!(u32,$base_value,numtoa_u32,u32,u32_padded,$u32_needed_size); - impl_numtoa_streamlined_for_type!(u64,$base_value,numtoa_u64,u64,u64_padded,$u64_needed_size); - impl_numtoa_streamlined_for_type!(u128,$base_value,numtoa_u128,u128,u128_padded,$u128_needed_size); - impl_numtoa_streamlined_for_type!(i8,$base_value,numtoa_i8,i8,i8_padded,$i8_needed_size); - impl_numtoa_streamlined_for_type!(i16,$base_value,numtoa_i16,i16,i16_padded,$i16_needed_size); - impl_numtoa_streamlined_for_type!(i32,$base_value,numtoa_i32,i32,i32_padded,$i32_needed_size); - impl_numtoa_streamlined_for_type!(i64,$base_value,numtoa_i64,i64,i64_padded,$i64_needed_size); - impl_numtoa_streamlined_for_type!(i128,$base_value,numtoa_i128,i128,i128_padded,$i128_needed_size); + use numtoa_uninit_u8; + use numtoa_uninit_u16; + use numtoa_uninit_u32; + use numtoa_uninit_u64; + use numtoa_uninit_u128; + use numtoa_uninit_i8; + use numtoa_uninit_i16; + use numtoa_uninit_i32; + use numtoa_uninit_i64; + use numtoa_uninit_i128; + + impl_numtoa_streamlined_for_type!(u8,$base_value,numtoa_uninit_u8,u8,u8_padded,$u8_needed_size); + impl_numtoa_streamlined_for_type!(u16,$base_value,numtoa_uninit_u16,u16,u16_padded,$u16_needed_size); + impl_numtoa_streamlined_for_type!(u32,$base_value,numtoa_uninit_u32,u32,u32_padded,$u32_needed_size); + impl_numtoa_streamlined_for_type!(u64,$base_value,numtoa_uninit_u64,u64,u64_padded,$u64_needed_size); + impl_numtoa_streamlined_for_type!(u128,$base_value,numtoa_uninit_u128,u128,u128_padded,$u128_needed_size); + impl_numtoa_streamlined_for_type!(i8,$base_value,numtoa_uninit_i8,i8,i8_padded,$i8_needed_size); + impl_numtoa_streamlined_for_type!(i16,$base_value,numtoa_uninit_i16,i16,i16_padded,$i16_needed_size); + impl_numtoa_streamlined_for_type!(i32,$base_value,numtoa_uninit_i32,i32,i32_padded,$i32_needed_size); + impl_numtoa_streamlined_for_type!(i64,$base_value,numtoa_uninit_i64,i64,i64_padded,$i64_needed_size); + impl_numtoa_streamlined_for_type!(i128,$base_value,numtoa_uninit_i128,i128,i128_padded,$i128_needed_size); } }; } @@ -624,6 +708,11 @@ fn base_too_high() { numtoa_i32(36, 37, &mut [0u8; 100]); } +#[test] +fn str_convenience_uninit() { + assert_eq!("256123", numtoa_uninit_i32_str(256123_i32, 10, &mut [MaybeUninit::uninit(); 20])); +} + #[test] fn str_convenience_core() { assert_eq!("256123", numtoa_i32_str(256123_i32, 10, &mut [0u8; 20])); @@ -634,6 +723,11 @@ fn str_convenience_core_u8() { assert_eq!("42", numtoa_u8_str(42u8, 10, &mut [b'X'; 20])); } +#[test] +fn str_convenience_core_u8_uninit() { + assert_eq!("42", numtoa_uninit_u8_str(42u8, 10, &mut [MaybeUninit::uninit(); 20])); +} + #[test] fn str_convenience_core_i8() { assert_eq!("42", numtoa_i8_str(42i8, 10, &mut [b'X'; 20])); @@ -644,6 +738,11 @@ fn str_convenience_trait() { assert_eq!("256123", 256123.numtoa_str(10, &mut [0u8; 20])); } +#[test] +fn str_convenience_trait_uninit() { + assert_eq!("256123", 256123.numtoa_uninit_str(10, &mut [MaybeUninit::uninit(); 20])); +} + #[test] fn str_convenience_base2() { assert_eq!("111110100001111011", base2::i32(256123).as_str());