@@ -367,7 +367,8 @@ impl AsciiChar {
367367 /// and `Some(AsciiChar::from_ascii_unchecked(128))` might be `None`.
368368 #[ inline]
369369 pub unsafe fn from_ascii_unchecked ( ch : u8 ) -> Self {
370- ch. to_ascii_char_unchecked ( )
370+ // SAFETY: Caller guarantees `ch` is within bounds of ascii.
371+ unsafe { ch. to_ascii_char_unchecked ( ) }
371372 }
372373
373374 /// Converts an ASCII character into a `u8`.
@@ -628,12 +629,20 @@ impl AsciiChar {
628629 /// assert_eq!(AsciiChar::new('p').as_printable_char(), 'p');
629630 /// ```
630631 pub fn as_printable_char ( self ) -> char {
631- unsafe {
632- match self as u8 {
633- b' ' ..=b'~' => self . as_char ( ) ,
634- 127 => '␡' ,
635- _ => char:: from_u32_unchecked ( self as u32 + '␀' as u32 ) ,
636- }
632+ match self as u8 {
633+ // Non printable characters
634+ // SAFETY: From codepoint 0x2400 ('␀') to 0x241f (`␟`), there are characters representing
635+ // the unprintable characters from 0x0 to 0x1f, ordered correctly.
636+ // As `b` is guaranteed to be within 0x0 to 0x1f, the conversion represents a
637+ // valid character.
638+ b @ 0x0 ..=0x1f => unsafe { char:: from_u32_unchecked ( u32:: from ( '␀' ) + u32:: from ( b) ) } ,
639+
640+ // 0x7f (delete) has it's own character at codepoint 0x2420, not 0x247f, so it is special
641+ // cased to return it's character
642+ 0x7f => '␡' ,
643+
644+ // All other characters are printable, and per function contract use `Self::as_char`
645+ _ => self . as_char ( ) ,
637646 }
638647 }
639648
@@ -781,17 +790,27 @@ impl Error for ToAsciiCharError {
781790
782791/// Convert `char`, `u8` and other character types to `AsciiChar`.
783792pub trait ToAsciiChar {
784- /// Convert to `AsciiChar` without checking that it is an ASCII character.
785- unsafe fn to_ascii_char_unchecked ( self ) -> AsciiChar ;
786793 /// Convert to `AsciiChar`.
787794 fn to_ascii_char ( self ) -> Result < AsciiChar , ToAsciiCharError > ;
795+
796+ /// Convert to `AsciiChar` without checking that it is an ASCII character.
797+ ///
798+ /// # Safety
799+ /// Calling this function with a value outside of the ascii range, `0x0` to `0x7f` inclusive,
800+ /// is undefined behavior.
801+ // TODO: Make sure this is the contract we want to express in this function.
802+ // It is ambigous if numbers such as `0xffffff20_u32` are valid ascii characters,
803+ // as this function returns `Ascii::Space` due to the cast to `u8`, even though
804+ // `to_ascii_char` returns `Err()`.
805+ unsafe fn to_ascii_char_unchecked ( self ) -> AsciiChar ;
788806}
789807
790808impl ToAsciiChar for AsciiChar {
791809 #[ inline]
792810 fn to_ascii_char ( self ) -> Result < AsciiChar , ToAsciiCharError > {
793811 Ok ( self )
794812 }
813+
795814 #[ inline]
796815 unsafe fn to_ascii_char_unchecked ( self ) -> AsciiChar {
797816 self
@@ -805,44 +824,56 @@ impl ToAsciiChar for u8 {
805824 }
806825 #[ inline]
807826 unsafe fn to_ascii_char_unchecked ( self ) -> AsciiChar {
808- mem:: transmute ( self )
827+ // SAFETY: Caller guarantees `self` is within bounds of the enum
828+ // variants, so this cast successfully produces a valid ascii
829+ // variant
830+ unsafe { mem:: transmute :: < u8 , AsciiChar > ( self ) }
809831 }
810832}
811833
834+ // Note: Casts to `u8` here does not cause problems, as the negative
835+ // range is mapped outside of ascii bounds.
812836impl ToAsciiChar for i8 {
813837 #[ inline]
814838 fn to_ascii_char ( self ) -> Result < AsciiChar , ToAsciiCharError > {
815- ( self as u32 ) . to_ascii_char ( )
839+ u32 :: from ( self as u8 ) . to_ascii_char ( )
816840 }
817841 #[ inline]
818842 unsafe fn to_ascii_char_unchecked ( self ) -> AsciiChar {
819- mem:: transmute ( self )
843+ // SAFETY: Caller guarantees `self` is within bounds of the enum
844+ // variants, so this cast successfully produces a valid ascii
845+ // variant
846+ unsafe { mem:: transmute :: < u8 , AsciiChar > ( self as u8 ) }
820847 }
821848}
822849
823850impl ToAsciiChar for char {
824851 #[ inline]
825852 fn to_ascii_char ( self ) -> Result < AsciiChar , ToAsciiCharError > {
826- ( self as u32 ) . to_ascii_char ( )
853+ u32 :: from ( self ) . to_ascii_char ( )
827854 }
828855 #[ inline]
829856 unsafe fn to_ascii_char_unchecked ( self ) -> AsciiChar {
830- ( self as u32 ) . to_ascii_char_unchecked ( )
857+ // SAFETY: Caller guarantees we're within ascii range.
858+ unsafe { u32:: from ( self ) . to_ascii_char_unchecked ( ) }
831859 }
832860}
833861
834862impl ToAsciiChar for u32 {
835863 fn to_ascii_char ( self ) -> Result < AsciiChar , ToAsciiCharError > {
836- unsafe {
837- match self {
838- 0 ..=127 => Ok ( self . to_ascii_char_unchecked ( ) ) ,
839- _ => Err ( ToAsciiCharError ( ( ) ) ) ,
840- }
864+ match self {
865+ // SAFETY: We're within the valid ascii range in this branch.
866+ 0x0 ..=0x7f => Ok ( unsafe { self . to_ascii_char_unchecked ( ) } ) ,
867+ _ => Err ( ToAsciiCharError ( ( ) ) ) ,
841868 }
842869 }
870+
843871 #[ inline]
844872 unsafe fn to_ascii_char_unchecked ( self ) -> AsciiChar {
845- ( self as u8 ) . to_ascii_char_unchecked ( )
873+ // Note: This cast discards the top bytes, this may cause problems, see
874+ // the TODO on this method's documentation in the trait.
875+ // SAFETY: Caller guarantees we're within ascii range.
876+ unsafe { ( self as u8 ) . to_ascii_char_unchecked ( ) }
846877 }
847878}
848879
@@ -852,7 +883,10 @@ impl ToAsciiChar for u16 {
852883 }
853884 #[ inline]
854885 unsafe fn to_ascii_char_unchecked ( self ) -> AsciiChar {
855- ( self as u8 ) . to_ascii_char_unchecked ( )
886+ // Note: This cast discards the top bytes, this may cause problems, see
887+ // the TODO on this method's documentation in the trait.
888+ // SAFETY: Caller guarantees we're within ascii range.
889+ unsafe { ( self as u8 ) . to_ascii_char_unchecked ( ) }
856890 }
857891}
858892
0 commit comments