#[inline]
pub(crate) fn find(haystack: &[u8], needle: &[u8], start: usize) -> Option<usize> {
let tail = haystack.get(start..)?;
find_in(tail, needle).map(|pos| pos + start)
}
#[cfg(feature = "memchr")]
#[inline]
fn find_in(haystack: &[u8], needle: &[u8]) -> Option<usize> {
match needle {
[byte] => memchr::memchr(*byte, haystack),
_ => memchr::memmem::find(haystack, needle),
}
}
#[cfg(not(feature = "memchr"))]
#[inline]
fn find_in(haystack: &[u8], needle: &[u8]) -> Option<usize> {
let (&first, rest) = needle.split_first()?;
if rest.is_empty() {
return find_byte(first, haystack);
}
let last_start = haystack.len().checked_sub(needle.len())?;
let mut from = 0;
while from <= last_start {
let candidate = from + find_byte(first, &haystack[from..=last_start])?;
let after = candidate + 1;
if haystack[after..after + rest.len()] == *rest {
return Some(candidate);
}
from = after;
}
None
}
#[cfg(any(not(feature = "memchr"), test))]
#[inline]
pub(crate) fn find_byte(needle: u8, haystack: &[u8]) -> Option<usize> {
const LO: u64 = 0x0101_0101_0101_0101;
const HI: u64 = 0x8080_8080_8080_8080;
let pattern = LO * u64::from(needle);
let flags = |chunk: &[u8]| -> u64 {
let mut word = [0u8; 8];
word.copy_from_slice(chunk);
let x = u64::from_le_bytes(word) ^ pattern;
x.wrapping_sub(LO) & !x & HI
};
let mut offset = 0;
let mut chunks = haystack.chunks_exact(16);
for chunk in &mut chunks {
let (lo, hi) = chunk.split_at(8);
let (a, b) = (flags(lo), flags(hi));
if a | b != 0 {
let hit = if a != 0 {
a.trailing_zeros()
} else {
64 + b.trailing_zeros()
};
return Some(offset + (hit / 8) as usize);
}
offset += 16;
}
chunks
.remainder()
.iter()
.position(|&b| b == needle)
.map(|pos| offset + pos)
}
#[cfg(test)]
mod tests {
use super::*;
fn naive(haystack: &[u8], needle: &[u8], start: usize) -> Option<usize> {
if needle.len() > haystack.len() {
return None;
}
(start..=haystack.len() - needle.len()).find(|&i| &haystack[i..i + needle.len()] == needle)
}
#[test]
fn find_byte_matches_position_for_every_offset() {
for len in 0..80 {
let mut hay = vec![0xAAu8; len];
assert_eq!(find_byte(b'\n', &hay), None);
for at in 0..len {
hay[at] = b'\n';
assert_eq!(find_byte(b'\n', &hay), Some(at), "len={len} at={at}");
if at + 1 < len {
hay[len - 1] = b'\n';
assert_eq!(find_byte(b'\n', &hay), Some(at));
hay[len - 1] = 0xAA;
}
hay[at] = 0xAA;
}
}
}
#[test]
fn find_byte_handles_borrow_propagation() {
let hay = [0x00u8, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
assert_eq!(find_byte(0x01, &hay), Some(1));
assert_eq!(find_byte(0x00, &hay), Some(0));
assert_eq!(find_byte(0x06, &hay), Some(8));
assert_eq!(find_byte(0xFF, &[0xFE; 33]), None);
}
#[test]
fn find_agrees_with_naive_search() {
let hay = b"a\r\r\nbc\r\n\r\r\r\nxyz\rabcabcd";
for needle in [&b"\r\n"[..], b"\n", b"\r", b"abcd", b"zz", b"\r\r\n", b"a"] {
for start in 0..=hay.len() + 1 {
assert_eq!(
find(hay, needle, start),
naive(hay, needle, start),
"{needle:?} {start}"
);
}
}
assert_eq!(find(b"ab", b"abc", 0), None);
assert_eq!(find(b"", b"a", 0), None);
}
}