Rust 创建带指针偏移量的字符串

Rust creating String with pointer offset

假设我有一个 String"Foo Bar",我想创建一个 "Bar" 的子字符串而不分配新内存。

所以我将原始字符串的原始指针移动到子字符串的开头(在本例中将其偏移 4)并使用 String::from_raw_parts() 函数创建字符串。

到目前为止,我有以下代码,据我所知应该可以很好地执行此操作。我只是不明白为什么这不起作用。

use std::mem;

fn main() {
    let s = String::from("Foo Bar");

    let ptr = s.as_ptr();

    mem::forget(s);

    unsafe {
        // no error when using ptr.add(0)
        let txt = String::from_raw_parts(ptr.add(4) as *mut _, 3, 3);

        println!("{:?}", txt); // This even prints "Bar" but crashes afterwards

        println!("prints because 'txt' is still in scope");
    }

    println!("won't print because 'txt' was dropped",)
}

我在 Windows 上收到以下错误:

error: process didn't exit successfully: `target\debug\main.exe` (exit code: 0xc0000374, STATUS_HEAP_CORRUPTION)

这些在 Linux (cargo 运行; cargo 运行 --release):

munmap_chunk(): invalid pointer

free(): invalid pointer

我认为它与 String 的析构函数有关,因为只要 txt 在范围内,程序 运行 就可以了。

另一件需要注意的事情是,当我使用 ptr.add(0) 而不是 ptr.add(4) 时,它 运行 没有错误。

另一方面,创建切片没有给我带来任何问题。删除它效果很好。

let t = slice::from_raw_parts(ptr.add(4), 3);

最后我想在不分配新内存的情况下将一个拥有的字符串拆分为多个拥有的 String

感谢任何帮助。

错误的原因是分配器的工作方式。 Undefined Behaviour 要求分配器释放一个它一开始没有给你的指针。在这种情况下,分配器为 s 分配了 7 个字节,并返回指向第一个字节的指针。然而,当 txt 被丢弃时,它告诉分配器释放指向字节 4 的指针,这是它以前从未见过的。这就是为什么当您 add(0) 而不是 add(4) 时没有问题。

正确使用unsafe困难,您应该尽可能避免它。


&str 类型的部分目的是允许共享所拥有的 string 的部分内容,因此我强烈建议您尽可能使用它们。

如果您不能单独使用 &str 的原因是您无法将生命周期追溯到原始 String,那么仍然有一些解决方案,不同 trade-offs:

  1. 泄漏内存,所以它实际上是静态的:

    let mut s = String::from("Foo Bar");
    let s = Box::leak(s.into_boxed_str());
    
    let txt: &'static str = &s[4..];
    let s: &'static str = &s[..4];
    

    显然,您只能在您的应用程序中执行几次,否则您将耗尽太多内存而无法取回。

  2. 使用 reference-counting 以确保原始 String 保留足够长的时间以使所有切片保持有效。这是草图解决方案:

    use std::{fmt, ops::Deref, rc::Rc};
    
    struct RcStr {
        rc: Rc<String>,
        start: usize,
        len: usize,
    }
    
    impl RcStr {
        fn from_rc_string(rc: Rc<String>, start: usize, len: usize) -> Self {
            RcStr { rc, start, len }
        }
    
        fn as_str(&self) -> &str {
            &self.rc[self.start..self.start + self.len]
        }
    }
    
    impl Deref for RcStr {
        type Target = str;
        fn deref(&self) -> &str {
            self.as_str()
        }
    }
    
    impl fmt::Display for RcStr {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            fmt::Display::fmt(self.as_str(), f)
        }
    }
    
    impl fmt::Debug for RcStr {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            fmt::Debug::fmt(self.as_str(), f)
        }
    }
    
    fn main() {
        let s = Rc::new(String::from("Foo Bar"));
    
        let txt = RcStr::from_rc_string(Rc::clone(&s), 4, 3);
        let s = RcStr::from_rc_string(Rc::clone(&s), 0, 4);
    
        println!("{:?}", txt); // "Bar"
        println!("{:?}", s);  // "Foo "
    }