一种以最小的开销将所有元素安全地从通用数组移到元组的方法 [英] Method for safely moving all elements out of a generic array into a tuple with minimal overhead

查看:72
本文介绍了一种以最小的开销将所有元素安全地从通用数组移到元组的方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在Rust中,我想将所有元素移出通用的固定宽度数组,以便随后分别移动它们.这些元素可能但不一定实现 Copy .我想出了以下解决方案:

In Rust, I want to move all the elements out of a generic fixed-width array so I may then move them individually. The elements may but don't necessarily implement Copy. I've come up with the following solution:

struct Vec3<T> {
    underlying_array: [T; 3]
}

impl<T> Vec3<T> {
    fn into_tuple(self) -> (T, T, T) {
        let result = (
            unsafe { mem::transmute_copy(&self.underlying_array[0]) },
            unsafe { mem::transmute_copy(&self.underlying_array[1]) },
            unsafe { mem::transmute_copy(&self.underlying_array[2]) },
        );
        mem::forget(self);
        result
    }
}

这似乎可行,但我想知道一般情况下它是否安全.来自C ++,通过复制对象的位模式并绕过源对象的析构函数来移动对象通常并不安全,我认为这实际上是我在这里所做的事情.但是在Rust中,每种类型都是可移动的(我认为),并且无法自定义移动语义(我认为),因此,除了按位复制外,我无法想到Rust可以在未优化的情况下实现移动对象的任何其他方式

It seems to work, but I want to know if it's safe in the general case. Coming from C++, it's not generally safe to move objects by copying their bit patterns and bypassing the source object's destructor, which I think is essentially what I'm doing here. But in Rust, every type is movable (I think) and there's no way to customize move semantics (I think), so I can't think of any other way Rust would implement moving objects in the un-optimized case than a bitwise copy.

是否正在像这样安全地将元素移出数组?有没有更惯用的方法呢?Rust编译器是否足够聪明,可以在可能的情况下取消 transmute_copy 的实际位复制,否则,是否有更快的方法?

Is moving elements out of an array like this safe? Is there a more idiomatic way to do it? Is the Rust compiler smart enough to elide the actual bit copying transmute_copy does when possible, and if not, is there a faster way to do this?

我认为这不是

I think this is not a duplicate of How do I move values out of an array? because in that example, the array elements aren't generic and don't implement Drop. The library in the accepted answer moves individual values out of the array one at a time while keeping the rest of the array usable. Since I want to move all the values at once and don't care about keeping the array, I think this case is different.

推荐答案

Rust 1.36及以上

现在可以用完全安全的代码完成此操作:

Rust 1.36 and up

This can now be done in completely safe code:

impl<T> Vec3<T> {
    fn into_tuple(self) -> (T, T, T) {
        let [a, b, c] = self.underlying_array;
        (a, b, c)
    }
}

以前的版本

您已经提到并在中讨论了如何将值移出数组?一袋零碎的类型可能非常危险.一旦我们复制了这些位并且它们是活动的",特质的实现(例如 Drop )就可以在我们最不期望的时候访问该值.

Previous versions

As you've mentioned and as discussed in How do I move values out of an array?, treating a generic type as a bag of bits can be very dangerous. Once we have copied those bits and they are "live", implementations of traits such as Drop can access the value when we least expect it.

话虽这么说,您当前的代码看来是安全的,但具有不必要的灵活性.使用 transmute transmute_copy 是The Big Hammer,实际上很少需要.您不希望能够更改值的类型.而是使用 ptr :: read .

That being said, your current code appears to be safe, but has needless flexibility. Using transmute or transmute_copy is The Big Hammer and is actually rarely needed. You don't want the ability to change the type of the value. Instead, use ptr::read.

常规做法是扩展 unsafe 块以覆盖使某些内容变得安全的代码范围,然后添加注释以说明为什么该块实际上是安全的.在这种情况下,我将其扩展为涵盖 mem :: forget ;.返回的表情也必须随身携带.

It's conventional to expand unsafe blocks to cover the range of code that makes something safe and then to include a comment explaining why the block is actually safe. In this case, I'd expand it to cover the mem::forget; the returned expression has to come along for the ride too.

您需要确保在您移出第一个值的时间到忘记数组之间的恐慌发生的可能性是不可能.否则,您的数组 将被半初始化,并且您 将触发未初始化的行为.在这种情况下,我喜欢您编写一个创建结果元组的语句的结构;很难在其中意外插入多余的代码.这也值得添加评论.

You will need to ensure that it's impossible for a panic to occur between the time that you've moved the first value out and when you forget the array. Otherwise your array will be half-initialized and you will trigger uninitialized behavior. In this case, I like your structure of writing a single statement that creates the resulting tuple; it's harder to accidentally shoehorn in extra code in there. This is also worth adding comments for.

use std::{mem, ptr};

struct Vec3<T> {
    underlying_array: [T; 3],
}

impl<T> Vec3<T> {
    fn into_tuple(self) -> (T, T, T) {
        // This is not safe because I copied it directly from Stack Overflow
        // without reading the prose associated with it that said I should 
        // write my own rationale for why this is safe.
        unsafe {
            let result = (
                ptr::read(&self.underlying_array[0]),
                ptr::read(&self.underlying_array[1]),
                ptr::read(&self.underlying_array[2]),
            );
            mem::forget(self);
            result
        }
    }
}

fn main() {}

每种类型都可以移动

every type is movable

是的,我相信这是正确的.您可以将某些值设置为不动(搜索一种情况".

Yes, I believe this to be correct. You can make certain values immovable though (search for "one specific case".

无法自定义移动语义

there's no way to customize move semantics

是的,我认为这是正确的.

Rust编译器足够聪明,可以消除实际的位复制

Is the Rust compiler smart enough to elide the actual bit copying

通常,我相信编译器会这样做,是的.但是,优化是一件棘手的事情.最终,仅在实际情况下查看汇编和配置文件即可告诉您真相.

In general, I would trust the compiler to do so, yes. However, optimization is a tricky thing. Ultimately, only looking at assembly and profiling in your real case will tell you the truth.

有更快的方法吗?

is there a faster way to do this?

不是我所知道的.

我通常会编写如下代码:

I'd normally write such code as:

extern crate arrayvec;
extern crate itertools;

use arrayvec::ArrayVec;
use itertools::Itertools;

struct Vec3<T> {
    underlying_array: [T; 3],
}

impl<T> Vec3<T> {
    fn into_tuple(self) -> (T, T, T) {
        ArrayVec::from(self.underlying_array).into_iter().next_tuple().unwrap()
    }
}

研究这两种实现的程序集,第一个使用25条x86_64指令,第二个使用69条指令.同样,我依靠性能分析来了解哪些实际上更快,因为更多的指令并没有这样做.一定意味着变慢.

Investigating the assembly for both implementations, the first one takes 25 x86_64 instructions and the second takes 69. Again, I'd rely on profiling to know which was actually faster, since more instructions doesn't necessarily mean slower.

另请参阅:

这篇关于一种以最小的开销将所有元素安全地从通用数组移到元组的方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆