- 你好,我知道代码可以完全写出来,没有任何不安全的代码,但我正在做一个研究和学习事情是如何工作的“引擎盖下”。
回到主题,我写了一段不安全的Rust代码,在我看来应该没有任何问题。
这就是定义:
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
}
// Making sure the value is never moved in the memory
let inner = Box::pin(input.to_string());
let separator_index = inner.find(Container::SEPARATOR)?;
let inner_ref = &**inner;
let half_a = &inner_ref[0..separator_index];
let half_b = &inner_ref[separator_index+1..];
// Check if the structure definition is met (populated values + only one separator)
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
}
}
}
总之,它接受任何可以表示为str引用的输入,在堆上创建输入的固定克隆,获取指向此值的两个半值的地址,并将其作为结构返回。
现在当我做一个测试:
let valid = Container::new("first-second").unwrap();
assert_eq!(valid.get_half_a(), "first");
assert_eq!(valid.get_half_b(), "second");
它应该运行没有任何恐慌,事实上,这就是发生在Windows上。它多次编译和运行都没有任何问题,但是当它在Ubuntu上运行时,我得到一个错误,显示地址不再指向内存中的有效位置:
thread 'tests::types::container' panicked at 'assertion failed: `(left == right)`
left: `"�K\u{13}϶"`,
right: `"first"`', research/src/tests/types.rs:77:5
有什么问题吗?我错过了什么吗?* 我将此代码作为GitHub操作运行,并带有以下标志runs-on: ubuntu-latest
。*
下面是一个指向playground的URL,展示了这段代码运行时没有任何问题:https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d36b19de4d0fa05340191f5107029d75
我预计在不同的操作系统上运行此代码不会有问题。
1条答案
按热度按时间t9aqgxwy1#
Changing
Box<String>
toBox<str>
不会影响可靠性,会触发MIRI。这来自
Box
,它不能被别名化。虽然通常可以从Box
派生指针,但当您移动Box
(通过返回Container
)时,Rust不再知道Box
有从它派生的指针,并假设通过指针的访问由于别名而无效。这就是为什么MIRI被触发。但是,我不确定是什么造成了这种未定义的行为。你的测试结果表明是,但不能告诉你为什么。我的猜测是Rust决定在
new
返回时立即删除inner
,因为它保证是唯一的。它甚至可以优化分配,使其永远不会实际写入任何数据(您的版本中String
的指针、长度和容量),因为这些数据永远不会被读取,这可以解释您的运行时错误。你可以通过只存储指针和实现
Drop
来解决这个问题。(playground)我不认为
Pin
在这里做任何事情。Pin
更多的用于处理公共接口。只要您不分发任何&mut
对inner
的引用,就没有什么需要防范的。虽然您可能希望它用于内部保证,但您的真实的保证比Pin
更强,因为您根本不能使用该值。