Rust <1.84中合并同原切片的子切片是否可行?如何解决Miri报错?
问题描述
我定义了一个存储切片的结构体:
struct S<'a> { slice: &'a [u8], }
我尝试编写代码合并两个该结构体的实例——显然只有当两个切片来自同一原始数据时才能成功。我通过检查指针范围(即[指针, 指针+长度])确认它们属于同一内存区域后,使用unsafe { core::slice::from_raw_parts(lower_ptr, new_length_elements) }重建切片,单元测试正常但Miri报错:
error: Undefined Behavior: trying to retag from <718383> for SharedReadOnly permission at alloc 486[0x14], but that tag does not exist in the borrow stack for this location --> ~/.rustup/toolchains/nightly-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/slice/raw.rs:138:9 | 138 | &*ptr::slice_from_raw_parts(data, len) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | trying to retag from <718383> for SharedReadOnly permission at alloc486[0x14], but that tag does not exist in the borrow stack for this location | this error occurs as part of retag at alloc486[0x8..0x18] | = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information help: <718383> was created by a SharedReadOnly retag at offsets [0x8..0x14] --> example/src/lib.rs:229:24 | 229 | let lower_ptr = self.slice.as_ptr(); | ^^^^^^^^^^^^^^^^^^^ = note: BACKTRACE (of the first span) on thread `tests::merge`: = note: inside `std::slice::from_raw_parts::<_, u32>` at ~/.rustup/toolchains/nightly-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/slice/raw.rs:138:9: 138:47
我想知道:能否从同一内存区域的两个子切片重建切片?如何操作才能避免Miri报错?
解决方案
可以从同一内存区域的两个子切片合并出更大的切片,但你的写法违反了Rust的**栈式借用(Stacked Borrows)**规则,导致Miri报错。
错误原因
你用self.slice.as_ptr()拿到的指针,其权限只覆盖self.slice对应的子区间。当你用这个指针创建覆盖更大范围的切片时,新切片的内存范围超出了原指针的权限标签覆盖的区域,Miri就会检测到这个未定义行为。
正确做法
要获取能覆盖整个原始内存区域的合法指针和生命周期,不能只依赖子切片的指针,得确保你拿到的是原始数据的完整借用。
方法1:在结构体中保留原始数据的引用
修改结构体,同时存储原始数据的完整引用,这样合并时可以直接基于原始引用创建新切片:
struct S<'a> { original: &'a [u8], slice: &'a [u8], } impl<'a> S<'a> { fn merge(&self, other: &S<'a>) -> Option<Self> { // 先检查两个子切片是否属于同一原始数据 if !std::ptr::eq(self.original.as_ptr(), other.original.as_ptr()) { return None; } // 计算合并后的起始和结束位置 let self_start = self.slice.as_ptr() as usize; let self_end = self_start + self.slice.len(); let other_start = other.slice.as_ptr() as usize; let other_end = other_start + other.slice.len(); let start = self_start.min(other_start); let end = self_end.max(other_end); // 基于原始引用的指针计算偏移,创建新切片 let offset = start - self.original.as_ptr() as usize; let len = end - start; let merged_slice = &self.original[offset..offset+len]; Some(Self { original: self.original, slice: merged_slice, }) } }
方法2:通过std::slice::from_raw_parts时,确保权限覆盖完整范围
如果你不想修改结构体,必须确保用来创建新切片的指针,其权限覆盖合并后的整个区间。可以通过获取原始数据的完整借用(比如从某个拥有原始数据的变量处),再基于它的指针来创建新切片,而不是用子切片的指针:
impl<'a> S<'a> { fn merge(&self, other: &S<'a>, original: &'a [u8]) -> Option<Self> { // 验证两个子切片都属于原始数据 let self_in_bounds = { let start = self.slice.as_ptr() as usize; let end = start + self.slice.len(); let orig_start = original.as_ptr() as usize; let orig_end = orig_start + original.len(); start >= orig_start && end <= orig_end }; let other_in_bounds = { let start = other.slice.as_ptr() as usize; let end = start + other.slice.len(); let orig_start = original.as_ptr() as usize; let orig_end = orig_start + original.len(); start >= orig_start && end <= orig_end }; if !self_in_bounds || !other_in_bounds { return None; } // 计算合并后的范围 let self_start = self.slice.as_ptr() as usize; let self_end = self_start + self.slice.len(); let other_start = other.slice.as_ptr() as usize; let other_end = other_start + other.slice.len(); let start = self_start.min(other_start); let len = self_end.max(other_end) - start; // 基于原始数据的指针创建新切片,确保权限覆盖完整范围 let merged_slice = unsafe { std::slice::from_raw_parts( original.as_ptr().add(start - original.as_ptr() as usize), len ) }; Some(Self { slice: merged_slice }) } }
关键要点
- 栈式借用规则要求,指针的权限标签必须覆盖你要访问的所有内存区域。子切片的指针只拥有对应子区间的权限,不能直接用来创建更大范围的切片。
- 必须基于覆盖完整目标范围的合法借用来创建新切片,要么保留原始数据的引用,要么从拥有原始数据的地方获取合法指针。
内容的提问来源于stack exchange,提问作者big_gie
相关产品推荐
相关产品推荐

