rust: str: implement several traits for CStr

Implement `Debug`, `Display`, `Deref` (into `BStr`), `AsRef<BStr>`
and a set of `Index<...>` traits.

This makes it `CStr` more convenient to use (and closer to `str`).

Co-developed-by: Alex Gaynor <alex.gaynor@gmail.com>
Signed-off-by: Alex Gaynor <alex.gaynor@gmail.com>
Co-developed-by: Morgan Bartlett <mjmouse9999@gmail.com>
Signed-off-by: Morgan Bartlett <mjmouse9999@gmail.com>
Signed-off-by: Gary Guo <gary@garyguo.net>
[Reworded, adapted for upstream and applied latest changes]
Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
This commit is contained in:
Gary Guo 2022-11-10 17:41:29 +01:00 committed by Miguel Ojeda
parent d126d23801
commit c07e67bd2d

View File

@ -2,7 +2,8 @@
//! String representations.
use core::fmt;
use core::fmt::{self, Write};
use core::ops::{self, Deref, Index};
use crate::{
bindings,
@ -199,6 +200,127 @@ impl CStr {
}
}
impl fmt::Display for CStr {
/// Formats printable ASCII characters, escaping the rest.
///
/// ```
/// # use kernel::c_str;
/// # use kernel::str::CStr;
/// # use kernel::str::CString;
/// let penguin = c_str!("🐧");
/// let s = CString::try_from_fmt(fmt!("{}", penguin)).unwrap();
/// assert_eq!(s.as_bytes_with_nul(), "\\xf0\\x9f\\x90\\xa7\0".as_bytes());
///
/// let ascii = c_str!("so \"cool\"");
/// let s = CString::try_from_fmt(fmt!("{}", ascii)).unwrap();
/// assert_eq!(s.as_bytes_with_nul(), "so \"cool\"\0".as_bytes());
/// ```
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for &c in self.as_bytes() {
if (0x20..0x7f).contains(&c) {
// Printable character.
f.write_char(c as char)?;
} else {
write!(f, "\\x{:02x}", c)?;
}
}
Ok(())
}
}
impl fmt::Debug for CStr {
/// Formats printable ASCII characters with a double quote on either end, escaping the rest.
///
/// ```
/// # use kernel::c_str;
/// # use kernel::str::CStr;
/// # use kernel::str::CString;
/// let penguin = c_str!("🐧");
/// let s = CString::try_from_fmt(fmt!("{:?}", penguin)).unwrap();
/// assert_eq!(s.as_bytes_with_nul(), "\"\\xf0\\x9f\\x90\\xa7\"\0".as_bytes());
///
/// // Embedded double quotes are escaped.
/// let ascii = c_str!("so \"cool\"");
/// let s = CString::try_from_fmt(fmt!("{:?}", ascii)).unwrap();
/// assert_eq!(s.as_bytes_with_nul(), "\"so \\\"cool\\\"\"\0".as_bytes());
/// ```
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("\"")?;
for &c in self.as_bytes() {
match c {
// Printable characters.
b'\"' => f.write_str("\\\"")?,
0x20..=0x7e => f.write_char(c as char)?,
_ => write!(f, "\\x{:02x}", c)?,
}
}
f.write_str("\"")
}
}
impl AsRef<BStr> for CStr {
#[inline]
fn as_ref(&self) -> &BStr {
self.as_bytes()
}
}
impl Deref for CStr {
type Target = BStr;
#[inline]
fn deref(&self) -> &Self::Target {
self.as_bytes()
}
}
impl Index<ops::RangeFrom<usize>> for CStr {
type Output = CStr;
#[inline]
fn index(&self, index: ops::RangeFrom<usize>) -> &Self::Output {
// Delegate bounds checking to slice.
// Assign to _ to mute clippy's unnecessary operation warning.
let _ = &self.as_bytes()[index.start..];
// SAFETY: We just checked the bounds.
unsafe { Self::from_bytes_with_nul_unchecked(&self.0[index.start..]) }
}
}
impl Index<ops::RangeFull> for CStr {
type Output = CStr;
#[inline]
fn index(&self, _index: ops::RangeFull) -> &Self::Output {
self
}
}
mod private {
use core::ops;
// Marker trait for index types that can be forward to `BStr`.
pub trait CStrIndex {}
impl CStrIndex for usize {}
impl CStrIndex for ops::Range<usize> {}
impl CStrIndex for ops::RangeInclusive<usize> {}
impl CStrIndex for ops::RangeToInclusive<usize> {}
}
impl<Idx> Index<Idx> for CStr
where
Idx: private::CStrIndex,
BStr: Index<Idx>,
{
type Output = <BStr as Index<Idx>>::Output;
#[inline]
fn index(&self, index: Idx) -> &Self::Output {
&self.as_bytes()[index]
}
}
/// Allows formatting of [`fmt::Arguments`] into a raw buffer.
///
/// It does not fail if callers write past the end of the buffer so that they can calculate the