Rust中类似于C++匿名结构体联合的语法是什么?

3

是否有Rust等效的以下C++示例(我为此问题编写):

union example {
    uint32_t fullValue;

    struct {
        unsigned sixteen1: 16;
        unsigned sixteen2: 16;
    };


    struct {
        unsigned three: 3;
        unsigned twentynine: 29;
    };

};

example e;
e.fullValue = 12345678;

std::cout << e.sixteen1 << ' ' << e.sixteen2 << ' ' << e.three << ' ' << e.twentynine;

参考一下,我正在编写一个CPU模拟器,并且能够轻松地将变量的二进制部分分离出来,并通过不同的名称引用它们,这样可以使代码更简单。我知道如何在C++中实现这一点(如上所述),但是我很难找出如何在Rust中实现等效操作。


3
我不太了解Rust,但我相当确定你在C++中所做的是未定义行为,参见https://stackoverflow.com/questions/67904738/is-it-undefined-behaviour-to-read-a-different-member-than-was-written-in-a-union。这在你的编译器和操作系统上可能有效,但一般而言是无法保证的。 - lukeg
@lukeg 你好,Luke,感谢您的评论。我知道这一点 - 问题仍然存在,因为我想在Rust中实现类似的功能。 - Phil
Rust也有unions,但我认为它们不能包含匿名字段,因此您需要使用类似于e.by_halves.sixteen1e.by_xxx.three的方式访问字段。 - Jmb
@Jmb 那仍然需要位域。 - Masklinn
在Rust中它不是UB,实际上... - Chayim Friedman
我几年前在C语言中遇到了UB,并且有着同样的问题。问题在于你试图通过定义数据边界来隐藏/消除某些操作。C需要生成掩码指令才能访问这些位 - 除了你正在进行的任何位操作之外。更不用说规范不能保证那些联合类型没有对齐空间。在我看来,你应该编写代码,使这些操作变得明确。这是做位操作等硬件级别工作的唯一好方法。基本上就像apilats的回答一样。 - James Newman
4个回答

6
你可以通过创建一个新类型结构体,并使用掩码和/或移位提取相关位来实现此功能。这段代码略微冗长(但不会太多),重要的是能避免你在 C++ 中引发未定义的行为。
#[derive(Debug, Clone, Copy)]
struct Example(pub u32);

impl Example {
    pub fn sixteen1(self) -> u32 {
        self.0 & 0xffff
    }
    pub fn sixteen2(self) -> u32 {
        self.0 >> 16
    }
    pub fn three(self) -> u32 {
        self.0 & 7
    }
    pub fn twentynine(self) -> u32 {
        self.0 >> 3
    }
}

pub fn main() {
    let e = Example(12345678);
    println!("{} {} {} {}", e.sixteen1(), e.sixteen2(), e.three(), e.twentynine());
}

1
注意:这些是getter,而不是setter。不确定OP是否要求setter,因为从他的问题中并不完全清楚。但C++联合也允许设置值。 - Finomnis
@Finomnis 不完全是这样,因为它处于纯UB领域。需要编写明确定义操作的代码。如果C++中没有UB,那么它只能通过生成您在此处编写的位操作来在硬件上工作。 - James Newman

3

更新

您可以创建一些宏来提取特定的位:

// Create a u32 mask that's all 0 except for one patch of 1's that
// begins at index `start` and continues for `len` digits.
macro_rules! mask {
    ($start:expr, $len:expr) => {
        {
            assert!($start >= 0);
            assert!($len > 0);
            assert!($start + $len <= 32);

            if $len == 32 {
                assert!($start == 0);
                0xffffffffu32
            } else {
                ((1u32 << $len) - 1) << $start
            }
        }
    }
}
const _: () = assert!(mask!(3, 7) == 0b1111111000);
const _: () = assert!(mask!(0, 32) == 0xffffffff);

// Select `num_bits` bits from `value` starting at `start`.
// For example, select_bits!(0xabcd1234, 8, 12) == 0xd12
// because the created mask is 0x000fff00.
macro_rules! select_bits {
    ($value:expr, $start:expr, $num_bits:expr) => {
        {
            let mask = mask!($start, $num_bits);
            ($value & mask) >> mask.trailing_zeros()
        }
    }
}
const _: () = assert!(select_bits!(0xabcd1234, 8, 12) == 0xd12);

然后,要么直接在 u32 上使用这些位,要么创建一个结构体来实现获取特定的位:
struct Example {
    v: u32,
}

impl Example {
    pub fn first_16(&self) -> u32 {
        select_bits!(self.v, 0, 16)
    }

    pub fn last_16(&self) -> u32 {
        select_bits!(self.v, 16, 16)
    }

    pub fn first_3(&self) -> u32 {
        select_bits!(self.v, 0, 3)
    }

    pub fn last_29(&self) -> u32 {
        select_bits!(self.v, 3, 29)
    }
}

fn main() {
    // Use hex for more easily checking the expected values.
    let e = Example { v: 0x12345678 };
    println!("{:x} {:x} {:x} {:x}", e.first_16(), e.last_16(), e.first_3(), e.last_29());

    // Or use decimal for checking with the provided C code.
    let e = Example { v: 12345678 };
    println!("{} {} {} {}", e.first_16(), e.last_16(), e.first_3(), e.last_29());
}

原始回答

虽然Rust确实有联合体,但对于您的用例而言,使用结构体并仅从结构体的单个值中获取位可能更好。

// Create a u32 mask that's all 0 except for one patch of 1's that
// begins at index `start` and continues for `len` digits.
macro_rules! mask {
    ($start:expr, $len:expr) => {
        {
            assert!($start >= 0);
            assert!($len > 0);
            assert!($start + $len <= 32);

            let mut mask = 0u32;
            for i in 0..$len {
                mask |= 1u32 << (i + $start);
            }

            mask
        }
    }
}

struct Example {
    v: u32,
}

impl Example {
    pub fn first_16(&self) -> u32 {
        self.get_bits(mask!(0, 16))
    }

    pub fn last_16(&self) -> u32 {
        self.get_bits(mask!(16, 16))
    }

    pub fn first_3(&self) -> u32 {
        self.get_bits(mask!(0, 3))
    }

    pub fn last_29(&self) -> u32 {
        self.get_bits(mask!(3, 29))
    }

    // Get the bits of `self.v` specified by `mask`.
    // Example:
    // self.v == 0xa9bf01f3
    // mask   == 0x00fff000
    // The result is 0xbf0
    fn get_bits(&self, mask: u32) -> u32 {
        // Find how many trailing zeros `mask` (in binary) has.
        // For example, the mask 0xa0 == 0b10100000 has 5.
        let mut trailing_zeros_count_of_mask = 0;
        while mask & (1u32 << trailing_zeros_count_of_mask) == 0 {
            trailing_zeros_count_of_mask += 1;
        }

        (self.v & mask) >> trailing_zeros_count_of_mask
    }
}

fn main() {
    // Use hex for more easily checking the expected values.
    let e = Example { v: 0x12345678 };
    println!("{:x} {:x} {:x} {:x}", e.first_16(), e.last_16(), e.first_3(), e.last_29());

    // Or use decimal for checking with the provided C code.
    let e = Example { v: 12345678 };
    println!("{} {} {} {}", e.first_16(), e.last_16(), e.first_3(), e.last_29());
}

这个设置使得选择任何你想要的位范围变得容易。比如说,如果你想要获取的中间16位,你只需要定义:

pub fn middle_16(&self) -> u32 {
    self.get_bits(mask!(8, 16))
}

“而且你甚至不需要结构体。不必让`get_bits()`成为一个方法,你可以定义它来接收一个`u32`值和掩码,然后定义像下面这样的函数。”
pub fn first_3(v: u32) -> u32 {
    get_bits(v, mask!(0, 3))
}

注意

我认为这个 Rust 代码在不考虑你的机器字节序的情况下可以正常工作,但是我只在我的小端字节序机器上运行过它。如果对你来说可能有问题,你应该仔细检查它。


1
请注意,“mask!”宏可以更简洁地编写为“((1 << len) - 1) << start”,只要正确处理移位32位的边缘情况即可。 - apilat

2
供参考,您的原始C++代码会输出
24910 188 6 1543209

现在Rust没有内置的位域功能,但有bitfield crate。
它允许指定一个新类型struct,然后为包装值的部分生成setter/getter。
例如,pub twentynine,set_twentynine: 31, 3;表示应该生成setter set_twentynine()和getter twentynine(),用于设置/获取位3到31,两者都包括在内。
因此,将C++联合转换为Rust bitfield,它可能看起来像这样:
use bitfield::bitfield;

bitfield! {
    pub struct Example (u32);

    pub full_value, set_full_value: 31, 0;

    pub sixteen1, set_sixteen1: 15, 0;
    pub sixteen2, set_sixteen2: 31, 16;

    pub three, set_three: 2, 0;
    pub twentynine, set_twentynine: 31, 3;
}

fn main() {
    let mut e = Example(0);
    e.set_full_value(12345678);

    println!(
        "{} {} {} {}",
        e.sixteen1(),
        e.sixteen2(),
        e.three(),
        e.twentynine()
    );
}

24910 188 6 1543209

请注意,那些自动生成的setter/getter方法非常小,很有可能被编译器内联,从而不会增加任何额外的开销。
当然,如果你想避免添加额外的依赖项,而是想手动实现getter/setter方法,请参考@apilat的答案。
替代方案:c2rust-bitfields crate:
use c2rust_bitfields::BitfieldStruct;

#[repr(C, align(1))]
#[derive(BitfieldStruct)]
struct Example {
    #[bitfield(name = "full_value", ty = "u32", bits = "0..=31")]
    #[bitfield(name = "sixteen1", ty = "u16", bits = "0..=15")]
    #[bitfield(name = "sixteen2", ty = "u16", bits = "16..=31")]
    #[bitfield(name = "three", ty = "u8", bits = "0..=2")]
    #[bitfield(name = "twentynine", ty = "u32", bits = "3..=31")]
    data: [u8; 4],
}

fn main() {
    let mut e = Example { data: [0; 4] };

    e.set_full_value(12345678);

    println!(
        "{} {} {} {}",
        e.sixteen1(),
        e.sixteen2(),
        e.three(),
        e.twentynine()
    );
}

24910 188 6 1543209

这个的优点是你可以自己指定联合部分的类型;第一个联合使用了u32

然而,我不确定字节序在这个联合中的作用。在不同字节序的系统上可能会产生不同的结果。可能需要进一步研究才能确定。


2
您可以使用bitfield crate。
在语法层面上,这似乎是您要寻找的东西的近似。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接