本文介绍了用于Rust中低级数据结构和类型转换的位域和联合的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要管理位域数据和联合。这是我在C语言中认为的代码:

I need to manage bitfield data and unions. Here is the code like I think it in C:

typedef struct __attribute__((__packed__)){
    union {
        struct __attribute__((__packed__)){
            unsigned short protocol : 4;
            unsigned short target : 12;
            unsigned short target_mode : 4;
            unsigned short source : 12;
            unsigned char cmd;
            unsigned char size;
        };
        unsigned char unmap[6]; // Unmapped form.
    };
}header_t;

我使用此联合可以轻松地从映射形式转换为未映射形式。我可以写到 header_t.protocol header_t.source 并将其作为 u8取回数组,使用 header_t.unmap 。这个开关没有时间,并且共享相同的内存块。

I use this union to switch easily from a mapped to an unmapped form. I can write to header_t.protocol or header_t.source and get it back as an u8 array using header_t.unmap. This switch uses no time and shares the same memory block.

我试图在Rust中做同样的事情,但是我没有找到一种干净的方法。我成功使用两个结构和专用的 impl 在两个结构之间进行切换:

I tried to do the same thing in Rust but I didn't find a clean way to do it. I succeeded in making it using two structures and a dedicated impl to switch between them:

#[allow(dead_code)]
pub struct Header {
    protocol:    u8,  // 4 bits used
    target:      u16, // 12 bits used
    target_mode: u8,  // 4 bits used
    source:      u16, // 12 bits used
    cmd:         u8,  // 8 bits used
    size:        u8,  // 8 bits used
}

#[allow(dead_code)]
pub struct UnmapHeader{
    tab:[u8; 6],
}

impl Header {
    #[allow(dead_code)]
    pub fn unmap(&self) -> UnmapHeader {
        let mut unmap_header = UnmapHeader { tab: [0; 6],};
        unmap_header.tab[0] = (self.protocol & 0b0000_1111) | (self.target << 4) as u8;
        unmap_header.tab[1] = (self.target >> 4) as u8;
        unmap_header.tab[2] = ((self.target_mode as u8) & 0b0000_1111) | (self.source << 4) as u8;
        unmap_header.tab[3] = (self.source >> 4) as u8;
        unmap_header.tab[4] = self.cmd;
        unmap_header.tab[5] = self.size;
        unmap_header
    }
}

impl UnmapHeader {
    #[allow(dead_code)]
    pub fn map(&self) -> Header {
        Header{
        protocol: self.tab[0] & 0b0000_1111,
        target: ((self.tab[0] & 0b1111_0000) >> 4) as u16 & (self.tab[1] << 4) as u16,
        target_mode: self.tab[2] & 0b0000_1111,
        source: ((self.tab[2] & 0b1111_0000) >> 4) as u16 & (self.tab[3] << 4) as u16,
        cmd: self.tab[4],
        size: self.tab[5],
        }
    }
}

#[test]
fn switch() {
    let header = Header {
        protocol: 0b0000_1000,
        target: 0b0000_0100_0000_0001,
        target_mode: 0b0000_0100,
        source: 0b0000_0100_0000_0001,
        cmd: 0xAA,
        size: 10,
    };
    let unmap_header = header.unmap();
    assert_eq!(unmap_header.tab[0], 0b0001_1000);
    assert_eq!(unmap_header.tab[1], 0b0100_0000);
    assert_eq!(unmap_header.tab[2], 0b0001_0100);
    assert_eq!(unmap_header.tab[3], 0b0100_0000);
    assert_eq!(unmap_header.tab[4], 0xAA);
    assert_eq!(unmap_header.tab[5], 10);
}

是否有更惯用的Rust解决方案?

Is there a more idiomatic Rust solution?

推荐答案

Rust(从最近开始)。但是,联合会要求使用不安全块,并且如果您不必与C联合会进行交互,就不是纯Rust代码的习惯用法。

Rust (since quite recently) supports C-style unions. However, unions require an unsafe block and are not idiomatic for pure Rust code if you don't have to interact with C unions.

一种方法是像 [u8; 6] ,然后提供更友好的访问器功能:

One approach is to model your underlying data just as a [u8; 6] and then provide more friendly accessor functions:

pub struct Header {
    tab: [u8; 6],
}

impl Header {
    pub fn get_protocol(&self) -> u8 {
        self.tab[0] & 0b0000_1111
    }

    pub fn set_protocol(&mut self, value: u8) {
        self.tab[0] = self.tab[0] & 0b1111_0000 | value & 0b0000_1111;
    }

    // etc..
}

正如您在问题注释之一中提到的那样,您可以使用来简化代码

As you mentioned in one of the question comments, you can keep the code simpler by using the bitfield crate.

另一种方法可能是使用各个字段定义结构,但转换为 [u8; 6] 。正如您所介绍的,这些字段所占空间比 [u8; 6] ,因此没有方便的转换(例如,不安全的 std :: mem :: transmute ),而无需在每个领域。因此,也许上面的解决方案更好。

Another approach could be to define your struct with the individual fields, but convert to [u8; 6]. As you have presented it though, the fields take up more space than the [u8; 6], so there isn't a convenient conversion (e.g. unsafe std::mem::transmute) without having to shift around the bits for each individual field anyway. So probably the solution above is better.

不管底层表示如何,在这种情况下定义友好的访问器可能是一个好主意。这是一种免费的抽象方法,可让您以后更改表示的主意,而不必更改其使用方式。

Regardless of the underlying representation, defining friendly accessors is probably a good idea in this situation. It's a cost-free abstraction, which will let you change your mind about the representation later without having to change how it is used.

这篇关于用于Rust中低级数据结构和类型转换的位域和联合的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-24 17:43