Skip to content

Commit f93f089

Browse files
phip1611nicholasbishop
authored andcommitted
EqStrUntilNul to compare Rust strings with CStr16
1 parent 593cd5e commit f93f089

File tree

3 files changed

+96
-1
lines changed

3 files changed

+96
-1
lines changed

src/data_types/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ mod enums;
119119

120120
mod strs;
121121
pub use self::strs::{
122-
CStr16, CStr8, FromSliceWithNulError, FromStrWithBufError, UnalignedCStr16,
122+
CStr16, CStr8, EqStrUntilNul, FromSliceWithNulError, FromStrWithBufError, UnalignedCStr16,
123123
UnalignedCStr16Error,
124124
};
125125

src/data_types/owned_strs.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use super::chars::{Char16, NUL_16};
22
use super::strs::{CStr16, FromSliceWithNulError};
33
use crate::alloc_api::vec::Vec;
4+
use crate::data_types::strs::EqStrUntilNul;
45
use core::fmt;
56
use core::ops;
67

@@ -15,6 +16,9 @@ pub enum FromStrError {
1516

1617
/// An owned UCS-2 null-terminated string.
1718
///
19+
/// For convenience, a [CString16] is comparable with `&str` and `String` from the standard library
20+
/// through the trait [EqStrUntilNul].
21+
///
1822
/// # Examples
1923
///
2024
/// Round-trip conversion from a [`&str`] to a `CString16` and back:
@@ -107,9 +111,17 @@ impl PartialEq<&CStr16> for CString16 {
107111
}
108112
}
109113

114+
impl<StrType: AsRef<str>> EqStrUntilNul<StrType> for CString16 {
115+
fn eq_str_until_nul(&self, other: &StrType) -> bool {
116+
let this = self.as_ref();
117+
this.eq_str_until_nul(other)
118+
}
119+
}
120+
110121
#[cfg(test)]
111122
mod tests {
112123
use super::*;
124+
use crate::alloc_api::string::String;
113125
use crate::alloc_api::vec;
114126

115127
#[test]
@@ -160,4 +172,18 @@ mod tests {
160172
crate::prelude::cstr16!("abc")
161173
);
162174
}
175+
176+
/// Tests the trait implementation of trait [EqStrUntilNul].
177+
#[test]
178+
fn test_cstring16_eq_std_str() {
179+
let input = CString16::try_from("test").unwrap();
180+
181+
// test various comparisons with different order (left, right)
182+
assert!(input.eq_str_until_nul(&"test"));
183+
assert!(input.eq_str_until_nul(&String::from("test")));
184+
185+
// now other direction
186+
assert!(String::from("test").eq_str_until_nul(&input));
187+
assert!("test".eq_str_until_nul(&input));
188+
}
163189
}

src/data_types/strs.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ impl CStr8 {
120120
///
121121
/// This type is largely inspired by `std::ffi::CStr`, see the documentation of
122122
/// `CStr` for more details on its semantics.
123+
///
124+
/// For convenience, a [CStr16] is comparable with `&str` and `String` from the standard library
125+
/// through the trait [EqStrUntilNul].
123126
#[derive(Eq, PartialEq)]
124127
#[repr(transparent)]
125128
pub struct CStr16([Char16]);
@@ -278,6 +281,22 @@ impl CStr16 {
278281
}
279282
}
280283

284+
impl<StrType: AsRef<str>> EqStrUntilNul<StrType> for CStr16 {
285+
fn eq_str_until_nul(&self, other: &StrType) -> bool {
286+
let other = other.as_ref();
287+
288+
let any_not_equal = self
289+
.iter()
290+
.copied()
291+
.map(char::from)
292+
.zip(other.chars())
293+
.take_while(|(l, r)| *l != '\0' && *r != '\0')
294+
.any(|(l, r)| l != r);
295+
296+
!any_not_equal
297+
}
298+
}
299+
281300
/// An iterator over `CStr16`.
282301
#[derive(Debug)]
283302
pub struct CStr16Iter<'a> {
@@ -427,9 +446,39 @@ impl<'a> UnalignedCStr16<'a> {
427446
}
428447
}
429448

449+
/// Trait that helps to compare Rust strings against CStr16 types.
450+
/// A generic implementation of this trait enables us that we only have to
451+
/// implement one direction (`left.eq_str_until_nul(&right)`) and we get
452+
/// the other direction (`right.eq_str_until_nul(&left)`) for free.
453+
pub trait EqStrUntilNul<StrType: ?Sized> {
454+
/// Checks if the provided Rust string `StrType` is equal to [Self] until the first null-byte
455+
/// is found. An exception is the terminating null-byte of [Self] which is ignored.
456+
///
457+
/// As soon as the first null byte in either `&self` or `other` is found, this method returns.
458+
/// Note that Rust strings are allowed to contain null-bytes that do not terminate the string.
459+
/// Although this is rather unusual, you can compare `"foo\0bar"` with an instance of [Self].
460+
/// In that case, only `foo"` is compared against [Self] (if [Self] is long enough).
461+
fn eq_str_until_nul(&self, other: &StrType) -> bool;
462+
}
463+
464+
// magic implementation which transforms an existing `left.eq_str_until_nul(&right)` implementation
465+
// into an additional working `right.eq_str_until_nul(&left)` implementation.
466+
impl<StrType, C16StrType> EqStrUntilNul<C16StrType> for StrType
467+
where
468+
StrType: AsRef<str>,
469+
C16StrType: EqStrUntilNul<StrType> + ?Sized,
470+
{
471+
fn eq_str_until_nul(&self, other: &C16StrType) -> bool {
472+
// reuse the existing implementation
473+
other.eq_str_until_nul(self)
474+
}
475+
}
476+
430477
#[cfg(test)]
431478
mod tests {
432479
use super::*;
480+
use crate::alloc_api::string::String;
481+
use uefi_macros::cstr16;
433482

434483
#[test]
435484
fn test_cstr16_num_bytes() {
@@ -515,4 +564,24 @@ mod tests {
515564
CString16::try_from("test").unwrap()
516565
);
517566
}
567+
568+
#[test]
569+
fn test_compare() {
570+
let input: &CStr16 = cstr16!("test");
571+
572+
// test various comparisons with different order (left, right)
573+
assert!(input.eq_str_until_nul(&"test"));
574+
assert!(input.eq_str_until_nul(&String::from("test")));
575+
576+
// now other direction
577+
assert!(String::from("test").eq_str_until_nul(input));
578+
assert!("test".eq_str_until_nul(input));
579+
580+
// some more tests
581+
// this is fine: compare until the first null
582+
assert!(input.eq_str_until_nul(&"te\0st"));
583+
// this is fine
584+
assert!(input.eq_str_until_nul(&"test\0"));
585+
assert!(!input.eq_str_until_nul(&"hello"));
586+
}
518587
}

0 commit comments

Comments
 (0)