I need to check if a given &str is a sub-slice of another &str, but I don't know what is the best way to do it. I wrote a function which does the job by using str::as_ptr() and pointer::offset_from(), but it has safety issues and I don't know how to handle them:
fn is_part_of(outer: &str, part: &str) -> bool {
// SAFETY: not handled
let offset = unsafe { part.as_ptr().offset_from(outer.as_ptr()) };
offset >= 0 && offset + (part.len() as isize) <= outer.len() as isize
}
fn main() {
let hello = "hello";
// Works as expected:
println!("{:?}", is_part_of(hello, &hello[1..5])); // true
// Works as expected, but as far as I understand, this is undefined behaviour:
println!("{:?}", is_part_of(hello, "ello")); // false
}
In the Rust doc for pointer::offset_from(), it is mentionned:
Both pointers must be derived from a pointer to the same object.
To my understanding, this is exactly what I don't want in my case.
Question
- How can I write the function
is_part_of()above in a clean and safe way ? - As a better alternative, is there a compile-time way to ensure that
a
&stris a sub-slice of another&str?
Context
I want to write a struct SubStr which contains a sub-slice of a bigger &str, and a reference to this bigger &str:
#[derive(Debug)]
pub struct SubStr<'src> {
source: &'src str,
part: &'src str, // MUST be a sub-slice of self.source
}
impl<'src> SubStr<'src> {
pub fn new(source: &'src str, part: &'src str) -> Self {
// Here, an assertion is missing, which would ensure that `part` is a sub-slice
// of `source`.
Self { source, part }
}
}
I need to keep track of the bigger string (self.source) because I want to be able to extend self.part by looking to what is remaining before/after its left/right boundaries, in self.source.
I.e., I want to implement such methods:
impl<'src> SubStr<'src> {
/// If we have:
/// self.source = "Hello foo world"
/// self.part = &self.source[10..15] (==> "world")
/// then updates `self.part` to `&self.source[6..15]` (==> "foo world")
pub fn extend_to_one_word_left(&mut self) {
self.part = unimplemented!();
}
/// If we have:
/// self.source = "Hello foo world"
/// self.part = &self.source[6..9] (==> "foo")
/// then updates `self.part` to `&self.source[5..10]` (==> " foo ")
pub fn untrim_spaces(&mut self) {
self.part = unimplemented!();
}
}