Official way to get the size of a field

It's not straightforward to compute the size of a struct field. This works for the cases I care about, but it took me quite some head-scratching to make it usable in a const context[1], and it cannot handle nested field access (contrast offset_of!, which can, but possibly only because it expands to a compiler intrinsic; $field:expr does not work at all):

pub const fn size_of_return_value<F, T, U>(_f: &F) -> usize
where F: FnOnce(T) -> U
{
    ::core::mem::size_of::<U>()
}

macro_rules! size_of_field {
    ($type:ty, $field:ident) => {
        $crate::size_of_return_value(&|s: $type| s.$field)
    }
}

struct Demo {
    a: u32,
    b: [u32; 3],
}

pub const SIZE_OF_DEMO_B: usize = size_of_field!(Demo, b);

So I'm wondering whether there should be an official API for getting the size of a struct field, and if so, how it should be spelled. core::mem::size_of_field! goes naturally with the existing size_of, offset_of, etc. and I imagine could be added as an ACP. Or, it'd be a much bigger change, and there may be obstacles I am not seeing, but I feel like it might be useful for a bunch of things, not just this, if StructType::field_name meant "the type of field field_name of StructType" when it appears in a type context. What do you think?


  1. The original idea for this technique is from https://stackoverflow.com/a/70224634. That version doesn't work in a const context, though. The change required to make it work is small but subtle and I'm honestly not sure why this version doesn't run foul of the same problem. ↩︎

1 Like

What is your use case for this? Why do you need to know the size of a field in a context where you don't always know its type?

It is not that I don't know the type but rather that I do not want to name the type anywhere besides the definition of the struct itself. Like for example if I have

struct S {
   // ... other fields ...
   blob: [u8; 32],
}

and then somewhere else

fn set_blob(s: &mut S, data: &[u8]) {
    s.blob.copy_from_slice(data[..size_of_field!(S, blob)]);
}

clearly it would be bad to have [..32] where I have [..size_of_field!(S, blob)], and I would argue that even [..BLOB_LEN] is inferior.

4 Likes

I'm not sure if this is a great example, because I would personally write it as:

fn set_blob(s: &mut S, data: &[u8]) {
    s.blob.copy_from_slice(data[..s.blob.len()]);
}

Can you provide another example?

The somewhat less cut-down version of the set_blob example involves a property-based test:

struct S {
   // ... other fields ...
   blob: [u8; 32],
}

proptest! {
    #[test]
    fn test_something_with_blobs(blob in [any::<u8>(); size_of_field!(S, blob)]) {
        let expectation = expected_value_for_blob(&blob);
        let s_instance = S { blob, /* etc */ }
        assert_eq!(s_instance.some_method(), expectation);
    }
}

In this construct there is no instance available at the point where I need to state the size and therefore .len() is not usable.

Tomorrow I will try to come up with an example not involving arrays/slices.

Handling nested field access is not that hard:

macro_rules! size_of_field {
    ($type:ty, $($field:ident).+) => {
        $crate::size_of_return_value(&|s: $type| s.$($field).+)
    }
}

But this cannot handle tuple fields, doing that will require tt-munching.

1 Like

It can if your macro takes tts instead of idents. Although of course that allows a bunch of invalid inputs too...

fn set_blob(s: &mut S, data: &[u8]) {
    s.blob = *data.first_chunk().unwrap();
}
1 Like

Looks like const size_of_val() is coming in 1.85: stabilize const_{size,align}_of_val by RalfJung · Pull Request #133762 · rust-lang/rust · GitHub

Might be all you need?

No, because there’s no val in a function signature.

This does seem like there’s a narrow question of “how can I get the length of an array that’s in a struct”, which in turn could be derived from “how can I get the size of a struct field”, which likewise would be trivial if we had “how can I get the type of a struct field (in a type context)”, which itself could be a specific form of “how do I get the type of an expression”. I’m not saying that we should jump to the highest abstraction here, but if we think there’ll be meaningful progress on one of the more general features, maybe we don’t need to build the specific one.

5 Likes

Just to be clear, I do in fact need "how can I get the size of any struct field" for my current project, and not just "how can I get the length of an array that happens to be a struct field". I do not currently need the former in const context, but it is extremely annoying whenever an intrinsic that always evaluates to a compile-time constant isn't actually usable in const context.

My use case for the general "how can I get the size of any struct field" is validation of FFI struct layout. I have a build script that parses a C header, makes a list of every field of every struct defined in that header, and generates a C source file that computes the size and offset of each field and packs them all into a table. On the Rust side, there are hand-written #[repr(C)] Rust structs for each C struct. (I can't use bindgen because of weirdnesses in the C header.)

My unit tests then compare the layout of each Rust struct with the corresponding C struct using code like this:

macro_rules! assert_field_layout {
    ($layout:expr, $type:ty, $n:expr, $field:ident) => {
        assert_eq!(stringify!($field), $layout.fields[$n].name);
        assert_eq!(
            offset_of!($type, $field),
            $layout.fields[$n].offset.try_into().unwrap()
        );
        assert_eq!(
            size_of!($type, $field),
            $layout.fields[$n].size.try_into().unwrap()
        );
    };
}

#[test]
fn layout_dm_target_deps() {
    let layout = &uapi::structs()["dm_target_deps"];
    assert_eq!(size_of!(dm_target_deps), layout.size);
    assert_eq!(align_of::<dm_target_deps>(), layout.align);
    assert_eq!(3, layout.fields.len());

    assert_field_layout!(layout, dm_target_deps, 0, count);
    assert_field_layout!(layout, dm_target_deps, 1, padding);
    assert_field_layout!(layout, dm_target_deps, 2, dev);
}
2 Likes

since noone has mentioned it yet: the ideomatic way to do this would probably be with a derive macro and a trait, although that ofc doesn't work in const contexts (yet)

I feel that I should not have to drag in all the machinery of procedural macros just to persuade the compiler to give me a piece of information that it has to carry around internally anyway.

1 Like