- RFC PR: rust-lang/rfcs#3113
- Rust Issue: rust-lang/rust#0000
Summary
This RFC adds support for bit-fields in repr(C) structs by
- Introducing a new attribute
bits(N)that can be applied to integer fields. - Allowing such annotated fields to be unnamed.
Motivation
The Linux kernel user-space API contains over 400 bit-fields. Writing the
corresponding types in Rust poses significant problems because the layout of
structs that contain bit-fields varies between architectures and is hard to
calculate manually. Consider the following examples which were compiled with
Clang for the X-unknown-linux-gnu target:
-
Effects of unnamed bit-fields on the alignment:
;Arch sizeof(X)alignof(X)x86_64 3 1 aarch64 4 4 -
Location of bit-fields in the memory layout:
;Arch Bits of the memory layout of Xx86_64 aaaaaaaaa __cccBBB ddddddddmips64 aaaaaaaaa BBBccc__ dddddddd -
Dependence of the correct Rust type on the layout of previous fields:
;On
x86_64, forN = 1, the correct Rust type is[u8; 2]. ForN = 3, the correct Rust type isu16. In either case, the struct requires an additionalrepr(align(4))annotation
Guide-level explanation
This feature allows you to write C-compatible structs by applying the bits
attribute to fields:
corresponds to the following C struct
;
As you can see in the example, unnamed bit-fields in C are written with the
reserved identifier _ in Rust. When you have a struct with an unnamed
bit-field, you cannot access that field. On the other hand, you also do not have
to define a value for that field when constructing the struct:
let x = X ;
let X = x;
Just like in C, you cannot take the address of a bit-field:
The value of the N in bits(N) must be a non-negative integer literal.
This literal must not be larger than the bit-size of the type of the field.
In debug builds, when you write to a bit-field, Rust performs overflow checks. If the value to be stored does not fit in the bit-field, the overflow check fails and the operation panics:
Reference-level explanation
The struct grammar is modified:
| TupleStruct
StructStruct :
- struct IDENTIFIER GenericParams? WhereClause? ( { StructFields? } | ; )
+ struct IDENTIFIER GenericParams? WhereClause? ( { StructBody? } | ; )
TupleStruct :
struct IDENTIFIER GenericParams? ( TupleFields? ) WhereClause? ;
-StructFields :
- StructField (, StructField)* ,?
+StructBody :
+ StructBodyElement (, StructBodyElement)* ,?
+
+StructBodyElement :
+ StructField
+ | UnnamedStructElement
StructField :
OuterAttribute*
Visibility?
IDENTIFIER : Type
+UnnamedStructElement :
+ OuterAttribute*
+ Visibility?
+ `_` : Type
+
TupleFields :
TupleField (, TupleField)* ,?
UnnamedStructElements look syntactically like StructFields except that their
names must be _ which cannot be the name of a StructField. Semantically, they
are not fields of the struct they are contained in.
UnnamedStructElements are used when determining the layout of the struct but are otherwise ignored. Since they are not fields, they cannot be accessed, do not appear in the construction of a struct, etc.
In the output of rustdoc, UnnamedStructElements are not distinguished from
StructFields except that their name is _.
The attribute bits(N) is added. This attribute can only be applied to
StructBodyElements of repr(C) structs. Such a StructBodyElement is
called a bit-field. N must be an integer literal. N is called the width
of the bit-field. A bit-field must have one of the types
bool,u8,u16,u32,u64,u128,usize,i8,i16,i32,i64,i128,isize
modulo type aliases.
N must be in the interval [0, 8 * size_of::<T>()] where T is the
type of the bit-field.
The attribute bitfield(0) can only be applied to UnnamedStructElements. An
UnnamedStructElement must be a bit-field.
Note that, despite being called a bit-field, a bit-field is not necessarily a field. To disambiguate, when talking about bit-field fields, we explicitly call them "bit-field fields".
Each field annotated with bits(N) occupies N bits of storage.
When reading and writing a bit-field field with type bool, bool is treated
like uM where M = 8 * size_of::<bool>() with true corresponding to 1 and
false corresponding to 0.
When a field annotated with bits(N) is read, the value has the type
of the field and the behavior is as follows:
- The
Nbits of storage occupied by the bit-field are read. - If the type of the field is signed (resp. unsigned), the bits are 1-extended
(resp. 0-extended) to the size of the type of the field. (1-extended means
that the new bits will have the value of the most significant bit. In
particular, bit-fields with signed types with width 1 can only store the
values
0and-1.) - The resulting bits are interpreted as a value of the type of the field. If the
bits are not a valid object representation of the type, the behavior is
undefined. This can only happen for bit-fields of type
bool.
When a field annotated with bits(N) is written, the value to be written
must have the type of the field and the behavior is as follows:
- If overflow checks are enabled and the value is outside the range of values that can be read from the field, the overflow check fails.
- The bitmask
(1 << N) - 1is applied to the value and the remainingNsignificant bits are written to the storage of the bit-field.
If the overflow check is performed at compile time, the behavior is subject to
the arithmetic_overflow lint.
By the layout of a struct containing bit-fields, we mean the following properties:
- The size and alignment of the struct.
- The offsets of all non-bit-field fields.
- For each bit-field field, the bits of the object representation used as its storage.
The language reference shall document for each target the layout of structs containing bit-fields.
The intended behavior is that the layout is the same layout as produced by the C system compiler of the target when compiling the corresponding C struct for the same target. (The C system compiler is the primary C compiler of the target. See Appendix A.) The corresponding C struct is constructed as follows:
- Translate the struct to the corresponding C struct as if the
bitfieldannotations were not present and as if_were a valid identifier for fields. - For each StructBodyElement that has a
bits(N)annotation in the Rust struct, append: Nto the declaration in the C struct. - For each field in the C struct whose name is
_, delete the field name.
If the thus created C struct is not a valid C struct on the target, the layout of the Rust struct is unspecified.
The & and &mut operators cannot be applied to bit-fields.
When a bit-field field is accessed, the abstract machine may also access adjacent bit-field fields but not fields that are separated from the field by a StructBodyElement that is not a bit-field field. (Note: This paragraph restricts the kinds of loads and stores the compiler can perform when accessing a bit-field. This paragraph does not need to be specially advertised to users as the inability to take references to bit-field fields makes it impossible to access adjacent bit-field fields in otherwise sound code.)
Drawbacks
- Bit-fields cannot be assigned without overflow checks.
- The annotation feels somewhat far away from the type itself.
Rationale and alternatives
-
Alternative: Using
u32: Nto specify the width of a bit-field. I've chosen to use an annotation instead because it seems more likely to be accepted. -
Alternative: Using a dedicated type instead of an annotation:
where
BitFieldwould be some kind of lang item that ensures that- the address of a field with such a type cannot be taken
- the type and width parameters are correct.
This type would then have the following API:
The layout of
BitField<T, N>would be the layout ofTwhen used outside a struct.This could then be used like so:
There is some magic happening here:
x.a += 1cannot be written asAddAssign::add(&mut x.a, 1)because the address ofx.acannot be taken. The compiler would figure it out when the lhs is a struct field.- When the assignment happens, the compiler writes the bits on the rhs to the correct positions in the struct.
This is a more structured solution but would need to be fleshed out.
Prior art
-
C
-
The crates bitfield and modular bitfield in the rust ecosystem tend to provide macros to generate bitfield-like structs. However, support for bitfields in
repr(C)structs brings in the language a high level of reliance that the compiler will correctly match the layout of the local C ABI and also adds more syntactic sugar. -
The author of Zig proposes a different syntax that basically boils down to arbitrarily sized integer types:
But this proposal is flawed because, in C, both the width and the underlying type influence the layout. The Zig proposal throws the underlying type away.
; ;These structs have different layouts on
x86_64-unknown-linux-gnu.
Unresolved questions
None right now.
Future possibilities
- Adding some way to perform wrapping assignment.
- Relaxing the requirement that
Nmust be a literal. - Bit-fields with types that are enums with integer representation.
- Bit-fields with types that are transparent wrapper structs around valid bit-field types.
- Bit-fields in
repr(C)unions. This is blocked primarily by the current restriction that unions must have at least one field and that one field must be named when the union is constructed. This creates questions around unions that only contain unnamed bit-fields. - Adding a way to opt into the
MSVClayout algorithm on*-windows-gnutargets on a per-type basis.
Appendix A
This appendix contains the LLVM targets supported by rustc grouped by their system compilers.
Clang
aarch64-apple-macosxaarch64-fuchsiaaarch64-linux-androidaarch64-unknown-freebsdaarch64-unknown-hermitaarch64-unknown-netbsdaarch64-unknown-noneaarch64-unknown-openbsdaarch64-unknown-redoxarm64-apple-iosarm64-apple-ios-macabiarm64-apple-tvosarmebv7r-unknown-none-eabiarmebv7r-unknown-none-eabihfarm-linux-androideabiarmv6-unknown-freebsd-gnueabihfarmv6-unknown-netbsdelf-eabihfarmv7a-none-eabiarmv7a-none-eabihfarmv7-apple-iosarmv7-none-linux-androidarmv7r-unknown-none-eabiarmv7r-unknown-none-eabihfarmv7s-apple-iosarmv7-unknown-freebsd-gnueabihfarmv7-unknown-netbsdelf-eabihfhexagon-unknown-linux-musli386-apple-iosi686-apple-macosxi686-linux-androidi686-unknown-freebsdi686-unknown-haikui686-unknown-netbsdelfi686-unknown-openbsdmipsel-sony-pspmipsel-unknown-nonemsp430-none-elfpowerpc64-unknown-freebsdpowerpc-unknown-linux-gnuspepowerpc-unknown-netbsdriscv32riscv64sparc64-unknown-netbsdsparc64-unknown-openbsdsparcv9-sun-solaristhumbv4t-none-eabithumbv6m-none-eabithumbv7em-none-eabithumbv7em-none-eabihfthumbv7m-none-eabithumbv8m.base-none-eabithumbv8m.main-none-eabithumbv8m.main-none-eabihfwasm32-unknown-emscriptenwasm32-unknown-unknownwasm32-wasix86_64-apple-iosx86_64-apple-ios-macabix86_64-apple-macosxx86_64-apple-tvosx86_64-elfx86_64-fuchsiax86_64-linux-androidx86_64-pc-solarisx86_64-rumprun-netbsdx86_64-unknown-dragonflyx86_64-unknown-freebsdx86_64-unknown-haikux86_64-unknown-hermitx86_64-unknown-l4re-uclibcx86_64-unknown-netbsdx86_64-unknown-openbsdx86_64-unknown-redox
GCC
aarch64-unknown-linux-gnuaarch64-unknown-linux-muslarm-unknown-linux-gnueabiarm-unknown-linux-gnueabihfarmv4t-unknown-linux-gnueabiarmv5te-unknown-linux-gnueabiarmv5te-unknown-linux-uclibcgnueabiarmv7-unknown-linux-gnueabiarmv7-unknown-linux-gnueabihfavr-unknown-unknowni586-unknown-linux-gnui586-unknown-linux-musli686-pc-windows-gnui686-unknown-linux-gnui686-unknown-linux-muslmips64el-unknown-linux-gnuabi64mips64el-unknown-linux-muslmips64-unknown-linux-gnuabi64mips64-unknown-linux-muslmipsel-unknown-linux-gnumipsel-unknown-linux-muslmipsel-unknown-linux-uclibcmipsisa32r6el-unknown-linux-gnumipsisa32r6-unknown-linux-gnumipsisa64r6el-unknown-linux-gnuabi64mipsisa64r6-unknown-linux-gnuabi64mips-unknown-linux-gnumips-unknown-linux-muslmips-unknown-linux-uclibcpowerpc64le-unknown-linux-gnupowerpc64le-unknown-linux-muslpowerpc64-unknown-linux-gnupowerpc64-unknown-linux-muslpowerpc-unknown-linux-gnupowerpc-unknown-linux-muslriscv32-unknown-linux-gnuriscv64-unknown-linux-gnus390x-unknown-linux-gnusparc64-unknown-linux-gnusparc-unknown-linux-gnux86_64-pc-windows-gnux86_64-unknown-linux-gnux86_64-unknown-linux-gnux32x86_64-unknown-linux-musl
MSVC
aarch64-pc-windows-msvci586-pc-windows-msvci686-pc-windows-msvci686-unknown-windowsthumbv7a-pc-windows-msvcx86_64-pc-windows-msvcx86_64-unknown-windows