Skip to content

Commit 15b71f4

Browse files
committed
Add CStr::bytes iterator
1 parent af69f4c commit 15b71f4

File tree

1 file changed

+89
-0
lines changed

1 file changed

+89
-0
lines changed

library/core/src/ffi/c_str.rs

+89
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ use crate::error::Error;
55
use crate::ffi::c_char;
66
use crate::fmt;
77
use crate::intrinsics;
8+
use crate::iter::FusedIterator;
9+
use crate::marker::PhantomData;
810
use crate::ops;
911
use crate::ptr::addr_of;
12+
use crate::ptr::NonNull;
1013
use crate::slice;
1114
use crate::slice::memchr;
1215
use crate::str;
@@ -617,6 +620,26 @@ impl CStr {
617620
unsafe { &*(addr_of!(self.inner) as *const [u8]) }
618621
}
619622

623+
/// Iterates over the bytes in this C string.
624+
///
625+
/// The returned iterator will **not** contain the trailing nul terminator
626+
/// that this C string has.
627+
///
628+
/// # Examples
629+
///
630+
/// ```
631+
/// #![feature(cstr_bytes)]
632+
/// use std::ffi::CStr;
633+
///
634+
/// let cstr = CStr::from_bytes_with_nul(b"foo\0").expect("CStr::from_bytes_with_nul failed");
635+
/// assert!(cstr.bytes().eq(*b"foo"));
636+
/// ```
637+
#[inline]
638+
#[unstable(feature = "cstr_bytes", issue = "112115")]
639+
pub fn bytes(&self) -> Bytes<'_> {
640+
Bytes::new(self)
641+
}
642+
620643
/// Yields a <code>&[str]</code> slice if the `CStr` contains valid UTF-8.
621644
///
622645
/// If the contents of the `CStr` are valid UTF-8 data, this
@@ -735,3 +758,69 @@ const unsafe fn const_strlen(ptr: *const c_char) -> usize {
735758
intrinsics::const_eval_select((ptr,), strlen_ct, strlen_rt)
736759
}
737760
}
761+
762+
/// An iterator over the bytes of a [`CStr`], without the nul terminator.
763+
///
764+
/// This struct is created by the [`bytes`] method on [`CStr`].
765+
/// See its documentation for more.
766+
///
767+
/// [`bytes`]: CStr::bytes
768+
#[must_use = "iterators are lazy and do nothing unless consumed"]
769+
#[unstable(feature = "cstr_bytes", issue = "112115")]
770+
#[derive(Clone, Debug)]
771+
pub struct Bytes<'a> {
772+
// since we know the string is nul-terminated, we only need one pointer
773+
ptr: NonNull<u8>,
774+
phantom: PhantomData<&'a u8>,
775+
}
776+
impl<'a> Bytes<'a> {
777+
#[inline]
778+
fn new(s: &'a CStr) -> Self {
779+
Self {
780+
// SAFETY: Because we have a valid reference to the string, we know
781+
// that its pointer is non-null.
782+
ptr: unsafe { NonNull::new_unchecked(s.as_ptr() as *const u8 as *mut u8) },
783+
phantom: PhantomData,
784+
}
785+
}
786+
787+
#[inline]
788+
fn is_empty(&self) -> bool {
789+
// SAFETY: We uphold that the pointer is always valid to dereference
790+
// by starting with a valid C string and then never incrementing beyond
791+
// the nul terminator.
792+
unsafe { *self.ptr.as_ref() == 0 }
793+
}
794+
}
795+
796+
#[unstable(feature = "cstr_bytes", issue = "112115")]
797+
impl Iterator for Bytes<'_> {
798+
type Item = u8;
799+
800+
#[inline]
801+
fn next(&mut self) -> Option<u8> {
802+
// SAFETY: We only choose a pointer from a valid C string, which must
803+
// be non-null and contain at least one value. Since we always stop at
804+
// the nul terminator, which is guaranteed to exist, we can assume that
805+
// the pointer is non-null and valid. This lets us safely dereference
806+
// it and assume that adding 1 will create a new, non-null, valid
807+
// pointer.
808+
unsafe {
809+
let ret = *self.ptr.as_ref();
810+
if ret == 0 {
811+
None
812+
} else {
813+
self.ptr = NonNull::new_unchecked(self.ptr.as_ptr().offset(1));
814+
Some(ret)
815+
}
816+
}
817+
}
818+
819+
#[inline]
820+
fn size_hint(&self) -> (usize, Option<usize>) {
821+
if self.is_empty() { (0, Some(0)) } else { (1, None) }
822+
}
823+
}
824+
825+
#[unstable(feature = "cstr_bytes", issue = "112115")]
826+
impl FusedIterator for Bytes<'_> {}

0 commit comments

Comments
 (0)