Skip to content

Commit 3b9e0fe

Browse files
committed
Auto merge of rust-lang#114443 - tgross35:cstr-len, r=dtolnay
Implement `cstr_count_bytes` This has not yet been approved via ACP, but it's simple enough to get started on. - ACP: rust-lang/libs-team#256 - Tracking issue: rust-lang#114441 `@rustbot` label +T-libs-api
2 parents bdb0fa3 + fe0eb8b commit 3b9e0fe

File tree

1 file changed

+72
-34
lines changed

1 file changed

+72
-34
lines changed

library/core/src/ffi/c_str.rs

+72-34
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,8 @@ impl CStr {
214214
/// * The memory referenced by the returned `CStr` must not be mutated for
215215
/// the duration of lifetime `'a`.
216216
///
217+
/// * The nul terminator must be within `isize::MAX` from `ptr`
218+
///
217219
/// > **Note**: This operation is intended to be a 0-cost cast but it is
218220
/// > currently implemented with an up-front calculation of the length of
219221
/// > the string. This is not guaranteed to always be the case.
@@ -259,42 +261,16 @@ impl CStr {
259261
#[rustc_const_unstable(feature = "const_cstr_from_ptr", issue = "113219")]
260262
pub const unsafe fn from_ptr<'a>(ptr: *const c_char) -> &'a CStr {
261263
// SAFETY: The caller has provided a pointer that points to a valid C
262-
// string with a NUL terminator of size less than `isize::MAX`, whose
263-
// content remain valid and doesn't change for the lifetime of the
264-
// returned `CStr`.
265-
//
266-
// Thus computing the length is fine (a NUL byte exists), the call to
267-
// from_raw_parts is safe because we know the length is at most `isize::MAX`, meaning
268-
// the call to `from_bytes_with_nul_unchecked` is correct.
264+
// string with a NUL terminator less than `isize::MAX` from `ptr`.
265+
let len = unsafe { const_strlen(ptr) };
266+
267+
// SAFETY: The caller has provided a valid pointer with length less than
268+
// `isize::MAX`, so `from_raw_parts` is safe. The content remains valid
269+
// and doesn't change for the lifetime of the returned `CStr`. This
270+
// means the call to `from_bytes_with_nul_unchecked` is correct.
269271
//
270272
// The cast from c_char to u8 is ok because a c_char is always one byte.
271-
unsafe {
272-
const fn strlen_ct(s: *const c_char) -> usize {
273-
let mut len = 0;
274-
275-
// SAFETY: Outer caller has provided a pointer to a valid C string.
276-
while unsafe { *s.add(len) } != 0 {
277-
len += 1;
278-
}
279-
280-
len
281-
}
282-
283-
// `inline` is necessary for codegen to see strlen.
284-
#[inline]
285-
fn strlen_rt(s: *const c_char) -> usize {
286-
extern "C" {
287-
/// Provided by libc or compiler_builtins.
288-
fn strlen(s: *const c_char) -> usize;
289-
}
290-
291-
// SAFETY: Outer caller has provided a pointer to a valid C string.
292-
unsafe { strlen(s) }
293-
}
294-
295-
let len = intrinsics::const_eval_select((ptr,), strlen_ct, strlen_rt);
296-
Self::from_bytes_with_nul_unchecked(slice::from_raw_parts(ptr.cast(), len + 1))
297-
}
273+
unsafe { Self::from_bytes_with_nul_unchecked(slice::from_raw_parts(ptr.cast(), len + 1)) }
298274
}
299275

300276
/// Creates a C string wrapper from a byte slice with any number of nuls.
@@ -516,6 +492,34 @@ impl CStr {
516492
self.inner.as_ptr()
517493
}
518494

495+
/// Returns the length of `self`. Like C's `strlen`, this does not include the nul terminator.
496+
///
497+
/// > **Note**: This method is currently implemented as a constant-time
498+
/// > cast, but it is planned to alter its definition in the future to
499+
/// > perform the length calculation whenever this method is called.
500+
///
501+
/// # Examples
502+
///
503+
/// ```
504+
/// #![feature(cstr_count_bytes)]
505+
///
506+
/// use std::ffi::CStr;
507+
///
508+
/// let cstr = CStr::from_bytes_with_nul(b"foo\0").unwrap();
509+
/// assert_eq!(cstr.count_bytes(), 3);
510+
///
511+
/// let cstr = CStr::from_bytes_with_nul(b"\0").unwrap();
512+
/// assert_eq!(cstr.count_bytes(), 0);
513+
/// ```
514+
#[inline]
515+
#[must_use]
516+
#[doc(alias("len", "strlen"))]
517+
#[unstable(feature = "cstr_count_bytes", issue = "114441")]
518+
#[rustc_const_unstable(feature = "const_cstr_from_ptr", issue = "113219")]
519+
pub const fn count_bytes(&self) -> usize {
520+
self.inner.len() - 1
521+
}
522+
519523
/// Returns `true` if `self.to_bytes()` has a length of 0.
520524
///
521525
/// # Examples
@@ -682,3 +686,37 @@ impl AsRef<CStr> for CStr {
682686
self
683687
}
684688
}
689+
690+
/// Calculate the length of a nul-terminated string. Defers to C's `strlen` when possible.
691+
///
692+
/// # Safety
693+
///
694+
/// The pointer must point to a valid buffer that contains a NUL terminator. The NUL must be
695+
/// located within `isize::MAX` from `ptr`.
696+
#[inline]
697+
const unsafe fn const_strlen(ptr: *const c_char) -> usize {
698+
const fn strlen_ct(s: *const c_char) -> usize {
699+
let mut len = 0;
700+
701+
// SAFETY: Outer caller has provided a pointer to a valid C string.
702+
while unsafe { *s.add(len) } != 0 {
703+
len += 1;
704+
}
705+
706+
len
707+
}
708+
709+
#[inline]
710+
fn strlen_rt(s: *const c_char) -> usize {
711+
extern "C" {
712+
/// Provided by libc or compiler_builtins.
713+
fn strlen(s: *const c_char) -> usize;
714+
}
715+
716+
// SAFETY: Outer caller has provided a pointer to a valid C string.
717+
unsafe { strlen(s) }
718+
}
719+
720+
// SAFETY: the two functions always provide equivalent functionality
721+
unsafe { intrinsics::const_eval_select((ptr,), strlen_ct, strlen_rt) }
722+
}

0 commit comments

Comments
 (0)