Rust 1.98: substr_range คืนช่วง byte ของ slice โดยไม่ค้นซ้ำ
งาน parser มักได้ &str ชิ้นเล็กจากข้อความต้นฉบับ แล้วภายหลังต้องรายงานตำแหน่ง byte กลับไปยัง editor หรือ diagnostic ครับ ก่อน Rust 1.98 คนจำนวนมากคำนวณจาก pointer เองหรือค้นข้อความย่อยซ้ำ ซึ่งทั้งอ่านยากและผิดได้เมื่อข้อความเหมือนกันหลายตำแหน่ง
WHY: ข้อความเท่ากันไม่ได้แปลว่ามาจากตำแหน่งเดียวกัน
สมมติ input มีคำว่า cache สองครั้ง การใช้ find กับค่าของ slice จะได้ตำแหน่งแรก ไม่จำเป็นต้องเป็น slice ที่ parser ส่งมา str::substr_range แก้โจทย์คนละแบบ: มันตรวจว่าสตริงที่ส่งเป็น subslice ของ receiver เดียวกัน แล้วคืนช่วง byte ที่ชี้กลับตำแหน่งจริง
แนวคิดนี้เหมือนเก็บเศษริบบิ้นที่ตัดจากม้วนเดิม เราไม่ได้ดูแค่ว่าสีเหมือนกัน แต่ตรวจว่าขอบของชิ้นนั้นอยู่ตรงไหนบนม้วน หากเป็นสตริงคนละ allocation แม้เนื้อหาเหมือนกันก็ไม่ถือว่าเป็นชิ้นจากม้วนเดียวกัน
HOW: เก็บ source และ slice ให้มีอายุสัมพันธ์กัน
โค้ดตัวอย่างนี้เป็นเพียง fragment สำหรับตรวจรูปแบบ API ของ Rust 1.98 และยังไม่ได้รันบน toolchain 1.96 ของงานนี้
fn locate_token(source: &str, token: &str) -> Option<std::ops::Range<usize>> {
source.substr_range(token)
}
fn main() {
let source = "mint cache, warm cache";
let token = &source[17..22];
assert_eq!(locate_token(source, token), Some(17..22));
let copied = token.to_owned();
assert_eq!(locate_token(source, &copied), None);
}
ตัวอย่างนี้ทำ static review เท่านั้น เพราะเครื่องตรวจของงานนี้เป็น Rust 1.96 ขณะที่ API เสถียรใน 1.98 ประเด็นสำคัญคือ Range
เลือกวิธีตามข้อมูลที่ถืออยู่
ถ้ามี subslice จาก source เดียวกันอยู่แล้ว substr_range เป็น default ที่ชัดที่สุด ถ้ามีเพียงข้อความที่ copy มา ต้องใช้ find หรือ parser index ที่เก็บไว้ตั้งแต่แรก ถ้าต้องรายงานตำแหน่งจำนวนมาก การให้ lexer ผลิต span พร้อม token มักประหยัดกว่าค้นหรือแปลงทุกครั้ง
อย่าสร้าง slice ด้วย byte index ที่ไม่ตรงขอบ UTF-8 เพราะ Rust จะ panic และอย่าเก็บ range แล้วแก้ source ก่อนนำกลับมาใช้ ตำแหน่งนั้นมีความหมายกับ snapshot เดิมเท่านั้น สำหรับ incremental editor ควรผูก span กับ revision หรือ document version
Recap
- substr_range ตรวจความสัมพันธ์ของ subslice ไม่ใช่แค่ความเท่ากันของข้อความ
- ผลลัพธ์เป็น byte range และต้องแปลงก่อนแสดง column บางรูปแบบ
- ข้อความที่ copy ไป allocation ใหม่จะไม่ผ่าน แม้เนื้อหาเหมือนเดิม
- งาน parser ขนาดใหญ่ยังควรสร้าง span ตั้งแต่ tokenization
ลองสำรวจจุดที่ใช้ pointer arithmetic หรือ find เพื่อกู้ตำแหน่ง แล้วแยกให้ชัดว่ามี subslice จริงหรือมีเพียงค่าข้อความครับ