yolenoyer
yolenoyer

Reputation: 9465

Check if a &str is a sub-slice of another &str

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

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!();
    }
}

Upvotes: 3

Views: 471

Answers (1)

user4815162342
user4815162342

Reputation: 155495

Instead of pointer::offset_from(), you can convert the pointer to usize and use usize arithmetic, which is entirely safe:

fn is_part_of(outer: &str, part: &str) -> bool {
    let outer_beg = outer.as_ptr() as usize;
    let outer_end = outer_beg + outer.len();
    let part_beg = part.as_ptr() as usize;
    let part_end = part_beg + part.len();
    part_beg >= outer_beg && part_end <= outer_end
}

Upvotes: 4

Related Questions