@@ -376,7 +376,8 @@ impl AsciiChar {
376376 #[ inline]
377377 #[ must_use]
378378 pub unsafe fn from_ascii_unchecked ( ch : u8 ) -> Self {
379- ch. to_ascii_char_unchecked ( )
379+ // SAFETY: Caller guarantees `ch` is within bounds of ascii.
380+ unsafe { ch. to_ascii_char_unchecked ( ) }
380381 }
381382
382383 /// Converts an ASCII character into a `u8`.
@@ -659,12 +660,20 @@ impl AsciiChar {
659660 /// ```
660661 #[ must_use]
661662 pub fn as_printable_char ( self ) -> char {
662- unsafe {
663- match self as u8 {
664- b' ' ..=b'~' => self . as_char ( ) ,
665- 127 => '␡' ,
666- _ => char:: from_u32_unchecked ( self as u32 + '␀' as u32 ) ,
667- }
663+ match self as u8 {
664+ // Non printable characters
665+ // SAFETY: From codepoint 0x2400 ('␀') to 0x241f (`␟`), there are characters representing
666+ // the unprintable characters from 0x0 to 0x1f, ordered correctly.
667+ // As `b` is guaranteed to be within 0x0 to 0x1f, the conversion represents a
668+ // valid character.
669+ b @ 0x0 ..=0x1f => unsafe { char:: from_u32_unchecked ( u32:: from ( '␀' ) + u32:: from ( b) ) } ,
670+
671+ // 0x7f (delete) has it's own character at codepoint 0x2420, not 0x247f, so it is special
672+ // cased to return it's character
673+ 0x7f => '␡' ,
674+
675+ // All other characters are printable, and per function contract use `Self::as_char`
676+ _ => self . as_char ( ) ,
668677 }
669678 }
670679
@@ -825,6 +834,14 @@ pub trait ToAsciiChar {
825834 fn to_ascii_char ( self ) -> Result < AsciiChar , ToAsciiCharError > ;
826835
827836 /// Convert to `AsciiChar` without checking that it is an ASCII character.
837+ ///
838+ /// # Safety
839+ /// Calling this function with a value outside of the ascii range, `0x0` to `0x7f` inclusive,
840+ /// is undefined behavior.
841+ // TODO: Make sure this is the contract we want to express in this function.
842+ // It is ambigous if numbers such as `0xffffff20_u32` are valid ascii characters,
843+ // as this function returns `Ascii::Space` due to the cast to `u8`, even though
844+ // `to_ascii_char` returns `Err()`.
828845 unsafe fn to_ascii_char_unchecked ( self ) -> AsciiChar ;
829846}
830847
@@ -833,6 +850,7 @@ impl ToAsciiChar for AsciiChar {
833850 fn to_ascii_char ( self ) -> Result < AsciiChar , ToAsciiCharError > {
834851 Ok ( self )
835852 }
853+
836854 #[ inline]
837855 unsafe fn to_ascii_char_unchecked ( self ) -> AsciiChar {
838856 self
@@ -846,7 +864,10 @@ impl ToAsciiChar for u8 {
846864 }
847865 #[ inline]
848866 unsafe fn to_ascii_char_unchecked ( self ) -> AsciiChar {
849- mem:: transmute ( self )
867+ // SAFETY: Caller guarantees `self` is within bounds of the enum
868+ // variants, so this cast successfully produces a valid ascii
869+ // variant
870+ unsafe { mem:: transmute :: < u8 , AsciiChar > ( self ) }
850871 }
851872}
852873
@@ -857,34 +878,38 @@ impl ToAsciiChar for u8 {
857878impl ToAsciiChar for i8 {
858879 #[ inline]
859880 fn to_ascii_char ( self ) -> Result < AsciiChar , ToAsciiCharError > {
860- ( self as u32 ) . to_ascii_char ( )
881+ u32 :: from ( self as u8 ) . to_ascii_char ( )
861882 }
862883 #[ inline]
863884 unsafe fn to_ascii_char_unchecked ( self ) -> AsciiChar {
864- mem:: transmute ( self )
885+ // SAFETY: Caller guarantees `self` is within bounds of the enum
886+ // variants, so this cast successfully produces a valid ascii
887+ // variant
888+ unsafe { mem:: transmute :: < u8 , AsciiChar > ( self as u8 ) }
865889 }
866890}
867891
868892impl ToAsciiChar for char {
869893 #[ inline]
870894 fn to_ascii_char ( self ) -> Result < AsciiChar , ToAsciiCharError > {
871- ( self as u32 ) . to_ascii_char ( )
895+ u32 :: from ( self ) . to_ascii_char ( )
872896 }
873897 #[ inline]
874898 unsafe fn to_ascii_char_unchecked ( self ) -> AsciiChar {
875- ( self as u32 ) . to_ascii_char_unchecked ( )
899+ // SAFETY: Caller guarantees we're within ascii range.
900+ unsafe { u32:: from ( self ) . to_ascii_char_unchecked ( ) }
876901 }
877902}
878903
879904impl ToAsciiChar for u32 {
880905 fn to_ascii_char ( self ) -> Result < AsciiChar , ToAsciiCharError > {
881- unsafe {
882- match self {
883- 0 ..=127 => Ok ( self . to_ascii_char_unchecked ( ) ) ,
884- _ => Err ( ToAsciiCharError ( ( ) ) ) ,
885- }
906+ match self {
907+ // SAFETY: We're within the valid ascii range in this branch.
908+ 0x0 ..=0x7f => Ok ( unsafe { self . to_ascii_char_unchecked ( ) } ) ,
909+ _ => Err ( ToAsciiCharError ( ( ) ) ) ,
886910 }
887911 }
912+
888913 #[ inline]
889914 unsafe fn to_ascii_char_unchecked ( self ) -> AsciiChar {
890915 // Note: This cast discards the top bytes, this may cause problems, see
0 commit comments