您好,我知道代码可以完全编写,没有任何不安全的代码,但我正在研究并学习“幕后”的工作原理。
回到主题,我写了一段不安全的 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
我预计在不同的操作系统上运行此代码不会出现任何问题。
Box<String>
更改为 Box<str>
,这不会影响健全性,但会触发 MIRI。
error: Undefined Behavior: trying to retag from <2563> for SharedReadOnly permission at alloc890[0x0], but that tag does not exist in the borrow stack for this location
--> src/main.rs:41:18
|
41 | unsafe { &*self.half_a }
| ^^^^^^^^^^^^^
| |
| trying to retag from <2563> for SharedReadOnly permission at alloc890[0x0], but that tag does not exist in the borrow stack for this location
| this error occurs as part of retag at alloc890[0x0..0x5]
|
= 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: <2563> was created by a SharedReadOnly retag at offsets [0x0..0x5]
--> src/main.rs:34:21
|
34 | half_a: half_a as *const str,
| ^^^^^^
help: <2563> was later invalidated at offsets [0x0..0xc] by a Unique retag (of a reference/box inside this compound value)
--> src/main.rs:36:13
|
36 | inner,
| ^^^^^
= note: BACKTRACE (of the first span):
= note: inside `Container::get_half_a` at src/main.rs:41:18: 41:31
note: inside `main`
--> src/main.rs:51:16
|
51 | assert_eq!(valid.get_half_a(), "first");
| ^^^^^^^^^^^^^^^^^^
这来自
Box
,不能使用别名。虽然从 Box
派生指针通常没问题,但当您移动 Box
(通过返回 Container
)时,Rust 不再知道 Box
具有从中派生的指针,并假设通过指针进行访问由于别名而无效。
这就是 MIRI 被触发的原因。但是,我不确定这是未定义的行为。你的测试结果表明确实如此。我的猜测是 Rust 决定一旦
inner
返回就可以删除 new
,因为它保证是唯一的。它甚至可能优化分配以永远不会实际写入任何数据(您版本中的指针、长度和String
的容量),因为该数据永远不会被读取,这可以解释您的运行时错误。
您可以通过仅存储指针并实现
Drop
来解决此问题。 (游乐场)
pub struct Container {
#[allow(dead_code)]
inner: *mut str,
half_a: *const str,
half_b: *const str,
}
impl Drop for Container {
fn drop(&mut self) {
// SAFETY: Nothing references this value since it is being dropped,
// and `half_a` and `half_b` are never read after this.
unsafe { drop(Box::from_raw(self.inner)) }
}
}
我不认为
Pin
在这里对健全性有任何作用。 Pin
更多地用于处理公共接口。只要您不分发任何对 &mut
的 inner
引用,就没有什么可防范的。虽然您可能需要它作为内部保证,但您的实际保证比 Pin
更强,因为您根本无法使用该值。