Skip to content

Commit 8f2771f

Browse files
committed
Add CStr::bytes iterator
1 parent a9251b6 commit 8f2771f

File tree

1 file changed

+91
-0
lines changed

1 file changed

+91
-0
lines changed

library/core/src/ffi/c_str.rs

+91
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ use crate::error::Error;
33
use crate::ffi::c_char;
44
use crate::fmt;
55
use crate::intrinsics;
6+
use crate::iter::FusedIterator;
7+
use crate::marker::PhantomData;
68
use crate::ops;
9+
use crate::ptr::NonNull;
710
use crate::slice;
811
use crate::slice::memchr;
912
use crate::str;
@@ -595,6 +598,26 @@ impl CStr {
595598
unsafe { &*(&self.inner as *const [c_char] as *const [u8]) }
596599
}
597600

601+
/// Iterates over the bytes in this C string.
602+
///
603+
/// The returned iterator will **not** contain the trailing nul terminator
604+
/// that this C string has.
605+
///
606+
/// # Examples
607+
///
608+
/// ```
609+
/// #![feature(cstr_bytes)]
610+
/// use std::ffi::CStr;
611+
///
612+
/// let cstr = CStr::from_bytes_with_nul(b"foo\0").expect("CStr::from_bytes_with_nul failed");
613+
/// assert!(cstr.bytes().eq(*b"foo"));
614+
/// ```
615+
#[inline]
616+
#[unstable(feature = "cstr_bytes", issue = "112115")]
617+
pub fn bytes(&self) -> CStrBytes<'_> {
618+
CStrBytes::new(self)
619+
}
620+
598621
/// Yields a <code>&[str]</code> slice if the `CStr` contains valid UTF-8.
599622
///
600623
/// If the contents of the `CStr` are valid UTF-8 data, this
@@ -675,3 +698,71 @@ impl AsRef<CStr> for CStr {
675698
self
676699
}
677700
}
701+
702+
/// An iterator over the bytes of a [`CStr`], without the nul terminator.
703+
///
704+
/// This struct is created by the [`bytes`] method on [`CStr`].
705+
/// See its documentation for more.
706+
///
707+
/// [`bytes`]: CStr::bytes
708+
#[must_use = "iterators are lazy and do nothing unless consumed"]
709+
#[unstable(feature = "cstr_bytes", issue = "112115")]
710+
#[derive(Clone, Debug)]
711+
pub struct CStrBytes<'a> {
712+
// since we know the string is nul-terminated, we only need one pointer
713+
ptr: NonNull<u8>,
714+
phantom: PhantomData<&'a u8>,
715+
}
716+
impl<'a> CStrBytes<'a> {
717+
#[inline]
718+
fn new(s: &'a CStr) -> Self {
719+
Self {
720+
// SAFETY: Because we have a valid reference to the string, we know
721+
// that its pointer is non-null.
722+
ptr: unsafe { NonNull::new_unchecked(s.inner.as_ptr() as *const u8 as *mut u8) },
723+
phantom: PhantomData,
724+
}
725+
}
726+
727+
#[inline]
728+
fn is_empty(&self) -> bool {
729+
// SAFETY: We uphold that the pointer is always valid to dereference
730+
// by starting with a valid C string and then never incrementing beyond
731+
// the nul terminator.
732+
unsafe { *self.ptr.as_ref() == 0 }
733+
}
734+
}
735+
736+
#[unstable(feature = "cstr_bytes", issue = "112115")]
737+
impl Iterator for CStrBytes<'_> {
738+
type Item = u8;
739+
740+
#[inline]
741+
fn next(&mut self) -> Option<u8> {
742+
// SAFETY: We only choose a pointer from a valid C string, which must
743+
// be non-null and contain at least one value. Since we always stop at
744+
// the nul terminator, which is guaranteed to exist, we can assume that
745+
// the pointer is non-null and valid. This lets us safely dereference
746+
// it and assume that adding 1 will create a new, non-null, valid
747+
// pointer.
748+
unsafe {
749+
intrinsics::assume(!self.ptr.as_ptr().is_null());
750+
751+
let ret = *self.ptr.as_ref();
752+
if ret == 0 {
753+
None
754+
} else {
755+
self.ptr = NonNull::new_unchecked(self.ptr.as_ptr().offset(1));
756+
Some(ret)
757+
}
758+
}
759+
}
760+
761+
#[inline]
762+
fn size_hint(&self) -> (usize, Option<usize>) {
763+
if self.is_empty() { (0, Some(0)) } else { (1, None) }
764+
}
765+
}
766+
767+
#[unstable(feature = "cstr_bytes", issue = "112115")]
768+
impl FusedIterator for CStrBytes<'_> {}

0 commit comments

Comments
 (0)