Skip to content

Commit

Permalink
Add CStr::bytes iterator
Browse files Browse the repository at this point in the history
  • Loading branch information
clarfonthey committed May 30, 2023
1 parent a9251b6 commit 8f2771f
Showing 1 changed file with 91 additions and 0 deletions.
91 changes: 91 additions & 0 deletions library/core/src/ffi/c_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ use crate::error::Error;
use crate::ffi::c_char;
use crate::fmt;
use crate::intrinsics;
use crate::iter::FusedIterator;
use crate::marker::PhantomData;
use crate::ops;
use crate::ptr::NonNull;
use crate::slice;
use crate::slice::memchr;
use crate::str;
Expand Down Expand Up @@ -595,6 +598,26 @@ impl CStr {
unsafe { &*(&self.inner as *const [c_char] as *const [u8]) }
}

/// Iterates over the bytes in this C string.
///
/// The returned iterator will **not** contain the trailing nul terminator
/// that this C string has.
///
/// # Examples
///
/// ```
/// #![feature(cstr_bytes)]
/// use std::ffi::CStr;
///
/// let cstr = CStr::from_bytes_with_nul(b"foo\0").expect("CStr::from_bytes_with_nul failed");
/// assert!(cstr.bytes().eq(*b"foo"));
/// ```
#[inline]
#[unstable(feature = "cstr_bytes", issue = "112115")]
pub fn bytes(&self) -> CStrBytes<'_> {
CStrBytes::new(self)
}

/// Yields a <code>&[str]</code> slice if the `CStr` contains valid UTF-8.
///
/// If the contents of the `CStr` are valid UTF-8 data, this
Expand Down Expand Up @@ -675,3 +698,71 @@ impl AsRef<CStr> for CStr {
self
}
}

/// An iterator over the bytes of a [`CStr`], without the nul terminator.
///
/// This struct is created by the [`bytes`] method on [`CStr`].
/// See its documentation for more.
///
/// [`bytes`]: CStr::bytes
#[must_use = "iterators are lazy and do nothing unless consumed"]
#[unstable(feature = "cstr_bytes", issue = "112115")]
#[derive(Clone, Debug)]
pub struct CStrBytes<'a> {
// since we know the string is nul-terminated, we only need one pointer
ptr: NonNull<u8>,
phantom: PhantomData<&'a u8>,
}
impl<'a> CStrBytes<'a> {
#[inline]
fn new(s: &'a CStr) -> Self {
Self {
// SAFETY: Because we have a valid reference to the string, we know
// that its pointer is non-null.
ptr: unsafe { NonNull::new_unchecked(s.inner.as_ptr() as *const u8 as *mut u8) },
phantom: PhantomData,
}
}

#[inline]
fn is_empty(&self) -> bool {
// SAFETY: We uphold that the pointer is always valid to dereference
// by starting with a valid C string and then never incrementing beyond
// the nul terminator.
unsafe { *self.ptr.as_ref() == 0 }
}
}

#[unstable(feature = "cstr_bytes", issue = "112115")]
impl Iterator for CStrBytes<'_> {
type Item = u8;

#[inline]
fn next(&mut self) -> Option<u8> {
// SAFETY: We only choose a pointer from a valid C string, which must
// be non-null and contain at least one value. Since we always stop at
// the nul terminator, which is guaranteed to exist, we can assume that
// the pointer is non-null and valid. This lets us safely dereference
// it and assume that adding 1 will create a new, non-null, valid
// pointer.
unsafe {
intrinsics::assume(!self.ptr.as_ptr().is_null());

let ret = *self.ptr.as_ref();
if ret == 0 {
None
} else {
self.ptr = NonNull::new_unchecked(self.ptr.as_ptr().offset(1));
Some(ret)
}
}
}

#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
if self.is_empty() { (0, Some(0)) } else { (1, None) }
}
}

#[unstable(feature = "cstr_bytes", issue = "112115")]
impl FusedIterator for CStrBytes<'_> {}

0 comments on commit 8f2771f

Please sign in to comment.