如何在安全的 Rust 中为未调整大小的类型及其拥有的对应物(如 `str` 和 `String`)创建新类型?

wch*_*gin 5 rust newtype

我想创建一对新类型Tag(str)and TagBuf(String),类似于 howPathPathBufwrap OsStrand OsString。我的最终目标是拥有一个TagBuf以 为键的地图,并且能够只用一个索引来索引它Tag

fn main() {
    let mut m: HashMap<TagBuf, i32> = HashMap::new();
    m.insert(TagBuf("x".to_string()), 1);
    assert_eq!(m.get(Tag::new("x")), Some(&1));
}
Run Code Online (Sandbox Code Playgroud)

但是我遇到了问题,因为它Tag是动态大小的。

具体来说,实施起来Borrow<Tag> for TagBuf很棘手:

pub struct Tag(str);
pub struct TagBuf(String);

impl std::borrow::Borrow<Tag> for TagBuf {
    fn borrow(&self) -> &Tag {
        let s: &str = self.0.as_str();
        // How can I turn `&str` into `&Tag`? A naive attempt fails:
        &Tag(*s)
    }
}
Run Code Online (Sandbox Code Playgroud)
error[E0277]: the size for values of type `str` cannot be known at compilation time
 --> src/lib.rs:8:10
  |
8 |         &Tag(*s)
  |          ^^^ doesn't have a size known at compile-time
  |
  = help: the trait `Sized` is not implemented for `str`
  = note: all function arguments must have a statically known size
Run Code Online (Sandbox Code Playgroud)

我可以只返回unsafe { std::mem::transmute(s) }一个 #[repr(transparent)]注释,但我想避免不安全的代码。

我查看了Path/的来源PathBuf得出以下结论:

use std::borrow::Borrow;
use std::ops::Deref;

#[repr(transparent)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Tag(str);
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
pub struct TagBuf(String);

impl Tag {
    fn new<S: AsRef<str> + ?Sized>(s: &S) -> &Tag {
        unsafe { &*(s.as_ref() as *const str as *const Tag) }
    }
}

impl Deref for TagBuf {
    type Target = Tag;
    fn deref(&self) -> &Tag {
        Tag::new(&self.0)
    }
}

impl Borrow<Tag> for TagBuf {
    fn borrow(&self) -> &Tag {
        self.deref()
    }
}

impl ToOwned for Tag {
    type Owned = TagBuf;
    fn to_owned(&self) -> TagBuf {
        TagBuf(self.0.to_owned())
    }
}

fn main() {
    let mut m = std::collections::HashMap::<TagBuf, i32>::new();
    m.insert(TagBuf("x".to_string()), 1);
    assert_eq!(m.get(Tag::new("x")), Some(&1));
}
Run Code Online (Sandbox Code Playgroud)

......这有效,我可以理解它(很好!),但它仍然 unsafe用于那个演员阵容,我想避免这种情况。

我看到了关于异国情调大小类型的 Rustonomicon 部分,它 使用unsafe,但调整大小的强制似乎很复杂,我不知道如何从[u8]to适应它str,因为没有对[u8; N].

我还阅读了 的实现Rc<str>,它似乎通过一些更不安全的转换Rc<[u8]>和一些我难以理解的专业化魔法来进行。

我已经阅读了一些相关的问题,例如:

……但我还没有找到答案。

最新是否稳定的锈有一种方法来定义NEWTYPE对用于strString在安全的代码?如果没有,是否有我应该遵循的 RFC 或跟踪问题?

She*_*ter 4

如果没有一些小的开销,这个问题就无法在安全的 Rust 中解决。

这就是我使用以下方法解决它的方法unsafe

use std::{borrow::Borrow, ops::Deref};

#[repr(transparent)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Tag(str);

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
pub struct TagBuf(String);

impl Tag {
    fn new<S: AsRef<str> + ?Sized>(s: &S) -> &Tag {
        unsafe { &*(s.as_ref() as *const str as *const Tag) }
    }
}

impl Deref for TagBuf {
    type Target = Tag;
    
    fn deref(&self) -> &Tag {
        Tag::new(&self.0)
    }
}

impl Borrow<Tag> for TagBuf {
    fn borrow(&self) -> &Tag {
        self.deref()
    }
}

impl ToOwned for Tag {
    type Owned = TagBuf;
    
    fn to_owned(&self) -> TagBuf {
        TagBuf(self.0.to_owned())
    }
}

use std::collections::HashMap;

fn main() {
    let mut m = HashMap::new();
    m.insert(TagBuf("x".to_string()), 1);
    assert_eq!(m.get(Tag::new("x")), Some(&1));
}
Run Code Online (Sandbox Code Playgroud)

也可以看看: