Hello, I know the code could be fully written without any unsafe code, but I am doing a research and learning how things work "under the hood".

Back to the topic,我已经编写了一段不安全的 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
        }
    }
}

总之,它接受任何可以表示为字符串引用的输入,在堆上创建输入的固定克隆,获取指向该值的两个半部分的地址,并将其作为 struct 返回.

现在,当我做测试时:

let valid = Container::new("first-second").unwrap();
assert_eq!(valid.get_half_a(), "first");
assert_eq!(valid.get_half_b(), "second");

它应该运行时不会出现任何panic ,事实上,这就是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

这里的问题可能是什么?我错过了什么吗? I am running this code as GitHub action with the following flag 100.

以下是操场的URL,显示此代码运行时没有任何问题: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d36b19de4d0fa05340191f5107029d75

我预计在不同的操作系统上运行这段代码不会出现任何问题.

推荐答案

Changing Box<String> to 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 决定,只要new返回,inner就可以被丢弃,因为它肯定是唯一的.它甚至可能优化分配,不实际写入任何数据(在您的版本中为String的指针、长度和容量),因为这些数据永远不会被读取,这将解释您的运行时错误.

您可以通过仅存储指针和实现Drop来修复此问题.(playground)

pub struct Container {
    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更多地用于处理公共接口.只要你不给出&mutinner的引用,就没有什么值得警惕的.虽然你可能想要它作为内部担保,但你真正的担保比Pin强,因为你根本不能使用它的价值.

Rust相关问答推荐

如何在tauri—leptos应用程序中监听后端值的变化?""

为什么要在WASM库中查看Rust函数需要`#[no_mangle]`?

当两者都有效时,为什么Rust编译器建议添加';&;而不是';*';?

在析构赋值中使用一些现有绑定

使用铁 rust S还原对多个数组执行顺序kronecker积

如何为 struct 字段设置新值并在Ruust中的可变方法中返回旧值

如何从ruust中的fig.toml中读取?

循环访问枚举中的不同集合

如何在Rust中基于字符串 Select struct ?

习语选项<;T>;到选项<;U>;当T->;U用From定义

随机函数不返回随机值

缺失serde的字段无法设置为默认值

borrow 匹配手臂内部的可变

仅当函数写为闭包时才会出现生命周期错误

在线程中运行时,TCPListener(服务器)在 ip 列表中的服务器实例之前没有从客户端接受所有客户端的请求

如何使用 rust bindgen 生成的 std_vector

字符串切片的向量超出范围但原始字符串仍然存在,为什么判断器说有错误?

我可以在不调用 .clone() 的情况下在类型转换期间重用 struct 字段吗?

深度嵌套枚举的清洁匹配臂

为什么-x试图解析为文字并在声明性宏中失败?