这段不安全Rust代码在Windows正常,Ubuntu运行出错的原因是什么?
问题分析与解决方案
你的代码在Linux上出现野指针问题,核心原因是引用生命周期绑定错误导致裸指针有效性未被保证,Windows上能运行只是编译器优化策略不同的侥幸结果。
具体问题点
你在new函数中通过let inner_ref = &**inner;创建的&str引用,其生命周期绑定的是栈上的临时变量inner(Pin<Box<String>>类型)。当你把inner移动到Container结构体中返回时,栈上的临时inner变量会被销毁,对应的引用生命周期也随之结束。
虽然堆上的String缓冲区还存在(因为inner的所有权转移到了Container),但基于过期引用转换的*const str裸指针,其有效性不再受Rust内存模型保障。Linux下的编译器会更严格地执行生命周期相关优化,导致该指针指向的内存被错误覆盖,出现乱码;而Windows下的编译器未触发该优化,代码侥幸运行。
修复方案
有两种可靠的修复方式,核心都是确保引用的生命周期与Container中的inner成员绑定:
方案1:修正引用来源,绑定正确生命周期
直接从inner成员获取&str,确保引用生命周期与inner绑定:
pub struct Container { inner: Pin<Box<String>>, half_a: *const str, half_b: *const str, } impl Container { const SEPARATOR: char = '-'; pub fn new(input: impl AsRef<str>) -> Option<Self> { let input = input.as_ref(); if input.is_empty() { return None; } let inner = Box::pin(input.to_string()); // 直接从inner获取绑定其生命周期的&str let inner_str = inner.as_ref().as_str(); let separator_index = inner_str.find(Container::SEPARATOR)?; let half_a = &inner_str[0..separator_index]; let half_b = &inner_str[separator_index+1..]; if half_a.is_empty() || half_b.is_empty() || half_b.contains(Container::SEPARATOR) { return None; } Some(Self { half_a: half_a as *const str, half_b: half_b as *const str, inner, }) } pub fn get_half_a(&self) -> &str { unsafe { &*self.half_a } } pub fn get_half_b(&self) -> &str { unsafe { &*self.half_b } } }
方案2:显式存储指针与长度(更推荐)
避免使用*const str胖指针,直接存储缓冲区指针和长度,手动构造&str,逻辑更清晰:
pub struct Container { inner: Pin<Box<String>>, half_a_ptr: *const u8, half_a_len: usize, half_b_ptr: *const u8, half_b_len: usize, } impl Container { const SEPARATOR: char = '-'; pub fn new(input: impl AsRef<str>) -> Option<Self> { let input = input.as_ref(); if input.is_empty() { return None; } let inner = Box::pin(input.to_string()); let inner_str = inner.as_ref().as_str(); let separator_index = inner_str.find(Container::SEPARATOR)?; let half_a = &inner_str[0..separator_index]; let half_b = &inner_str[separator_index+1..]; if half_a.is_empty() || half_b.is_empty() || half_b.contains(Container::SEPARATOR) { return None; } Some(Self { inner, half_a_ptr: half_a.as_ptr(), half_a_len: half_a.len(), half_b_ptr: half_b.as_ptr(), half_b_len: half_b.len(), }) } pub fn get_half_a(&self) -> &str { unsafe { std::str::from_raw_parts(self.half_a_ptr, self.half_a_len) } } pub fn get_half_b(&self) -> &str { unsafe { std::str::from_raw_parts(self.half_b_ptr, self.half_b_len) } } }
额外说明
你使用Pin<Box<String>>其实是多余的——Box<String>本身已经将String存储在堆上,移动Box不会改变堆上缓冲区的地址,Pin在这里无法提供额外的安全性。如果是为了学习自引用结构体,后续可以尝试更典型的自引用场景(比如结构体内部引用自身的栈成员)。
内容的提问来源于stack exchange,提问作者444RS
相关产品推荐
相关产品推荐

