A sound way to create a default "empty" instance of a Rust struct with type parameters, which wraps a Vec<T>?

huangapple go评论67阅读模式
英文:

A sound way to create a default "empty" instance of a Rust struct with type parameters, which wraps a Vec<T>?

问题

以下是代码部分的翻译:

/// 这是一个简单的结构体,只是一个围绕`Vec&lt;T&gt;`的包装器:
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub struct VecSet&lt;T: PartialOrd + Ord + PartialEq + Eq + Hash + Clone&gt; {
    pub(crate) data: Vec&lt;T&gt;,
}
static EMPTY_VEC_SET : VecSet&lt;&amp;str&gt; = VecSet { data : vec![] };

pub(crate) fn empty_vecset&lt;T: &#39;static + PartialOrd + Ord + PartialEq + Eq + Hash + Clone&gt;() -&gt; &amp;&#39;static VecSet&lt;T&gt; {
    // 我们需要能够返回一个对空vec集的引用;因为它将是不可变的,泛型类型是无关紧要的 - 只需分配一次并根据需要返回引用
    unsafe {
        let ptr = &amp;EMPTY_VEC_SET as *const VecSet&lt;&amp;str&gt;;
        let ptr2 = ptr as *const VecSet&lt;T&gt;
        &amp;*ptr2
    }
}

请注意,上述代码翻译中包含了转义字符(&lt;&amp;),这些字符在原始代码中是HTML实体,因此需要进行转义以在代码中显示正确。

英文:

I have a simple struct which is just a wrapper around a Vec&lt;T&gt;:

/// A set-like data structure uses a sorted `Vec` and binary-search.
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub struct VecSet&lt;T: PartialOrd + Ord + PartialEq + Eq + Hash + Clone&gt; {
    pub(crate) data: Vec&lt;T&gt;,
}

I need a way to return a borrow of an empty instance of that type - an instance over an empty Vec that will never be added to, will never have a mutable reference to it created. This is a library, and the concrete type of T is caller-defined, so there is not a finite set of possible types.

For a variety of reasons, returning Option&lt;VecSet&gt; would make a fairly clunky API for callers; allocating a new instance is not an option when returning a borrow.

Allocating a new instance on demand and using, say, lazy_static with type_map would work, but would also be, IMHO, overkill - this is literally an object that can never have anything done with it except report its emptiness (and empty results can be returned a lot).

Borrowing a pattern that is common in Java, where object size is not a consideration, what I came up with was to statically allocate an instance over a type (&amp;str arbitrarily chosen),
convert it to a raw pointer, and then coerce it to the generified type - the thinking being, if it cannot have contents, there should be no code that is dependent on the size of the backing memory buffer because that will always be zero.

static EMPTY_VEC_SET : VecSet&lt;&amp;str&gt; = VecSet { data : vec![] };

pub(crate) fn empty_vecset&lt;T: &#39;static + PartialOrd + Ord + PartialEq + Eq + Hash + Clone&gt;() -&gt; &amp;&#39;static VecSet&lt;T&gt; {
    // We need to be able to return a reference to an empty vec set; since it will be
    // immutable, the generic type is irrelevant - allocate once and return a reference as needed
    unsafe {
        let ptr = &amp;EMPTY_VEC_SET as *const VecSet&lt;&amp;str&gt;;
        let ptr2 = ptr as *const VecSet&lt;T&gt;;
        &amp;*ptr2
    }
}

Is this actually sound? Is there some nuance of statics being moved I could be missing? Is there a vastly simpler way of doing this I'm just not aware of?

答案1

得分: 3

以下是代码的翻译部分:

This is not sound, as noted in the comments, for a variety of reasons:

 - You cannot transmute a `Vec`, its layout is not guaranteed.
 - The data pointer may not be well aligned for `T`.

However, there is no reason to reach for unsafe code - you can create a generic `const`, it's just a bit clunky. You need to use an associated const:
```rust
fn empty_vec<T>() -> &'static Vec<T> {
    struct Foo<T>(T);
    impl<T: 'static> Foo<T> {
        const VEC: &'static Vec<T> = &Vec::new();
    }
    Foo::<T>::VEC
}

When inline_const will be stabilized, this will be easier:

#![feature(inline_const)]

fn empty_vec<T>() -> &'static Vec<T> {
    const { &Vec::new() }
}

<details>
<summary>英文:</summary>

This is not sound, as noted in the comments, for variety of reasons:

 - You cannot transmute a `Vec`, its layout is not guaranteed.
 - The data pointer may not be well aligned for `T`.

However, there is no reason to reach for unsafe code - you can create a generic `const`, it&#39;s just a bit clunky. You need to use an associated const:
```rust
fn empty_vec&lt;T&gt;() -&gt; &amp;&#39;static Vec&lt;T&gt; {
    struct Foo&lt;T&gt;(T);
    impl&lt;T: &#39;static&gt; Foo&lt;T&gt; {
        const VEC: &amp;&#39;static Vec&lt;T&gt; = &amp;Vec::new();
    }
    Foo::&lt;T&gt;::VEC
}

When inline_const will be stabilized, this will be easier:

#![feature(inline_const)]

fn empty_vec&lt;T&gt;() -&gt; &amp;&#39;static Vec&lt;T&gt; {
    const { &amp;Vec::new() }
}

huangapple
  • 本文由 发表于 2023年3月9日 14:18:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/75681026.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定