Skip to content

Implement Default聽#4975

Description

@tgross35

Discussed at #t-libs > Questions and polls about `libc` 1.0 @ 馃挰, we would like to have a Default impl for pretty much everything. This should be part of s!/s_no_extra_traits! but won't work out of the box with a derive.

In particular, we need a workaround for [T; N] where N > 32, and unions somehow.

Activity

  1. added this to the 0.2.x milestone on Feb 14, 2026
  2. tgross35 commented on Feb 16, 2026

    @tgross35
    MemberAuthor

    I think ideally we would accept a #[skip_derive_default] attribute in s! which forwards to a impl_default! macro. That macro could then look for a custom_default attribute use its contents rather than Default::default().

    For example, this:

    s! {
        #[skip_derive_default]
        struct foo {
            pub a: i32,
            #[custom_default([0u8; 1024])]
            pub b: [u8; 1024],
            #[custom_default(bar { a: 0 })]
            pub bar: bar,
        }
    
        union bar {
            a: i32,
            b: f32,
        }
    }

    Expands to this:

    #[derive(Clone, Copy, Debug, ....)]
    strip_custom_attributes! {
        struct foo {
            pub a: i32,
            #[custom_default([0u8; 1024])]
            pub b: [u8; 1024],
            #[custom_default(bar { a: 0 })]
            pub bar: bar,
        }
    }
    
    custom_default! {
        struct foo {
            pub a: i32,
            #[custom_default([0u8; 1024])]
            pub b: [u8; 1024],
            #[custom_default(bar { a: 0 })]
            pub bar: bar,
        }
    }
    
    // ignoring `union bar`

    Which then expands to:

    #[derive(Clone, Copy, Debug, ....)]
    struct foo {
        pub a: i32,
        pub b: [u8; 1024],
        pub bar: bar,
    }
    
    impl Default for foo {
        fn default() -> Self {
            Self {
                // For fields without `custom_default`, just use `Default::default()`
                a: Default::default(),
                // For `custom_default`, use that token tree
                b: [0u8; 1024],
                bar: bar { a: 0 },
            }
        }
    }
    
    // ignoring `union bar`

    This isn't going to be the most fun macro to write, but it's much preferred to dealing with hundreds of custom derives.

    I won't be working on this for a while but can provide guidance if anybody is interested.

  3. added
    E-mediumE-medium Call for participation: Medium difficulty. Experience needed to fix: Intermediate.
    E-help-wantedCall for participation: Help is requested to fix this issue.
    on Feb 16, 2026
  4. telcharr commented on Jun 15, 2026

    @telcharr
    Contributor

    Mind if I take this? I messed around with a prototype impl_default! that generates the impl field by field. Default::default() for normal fields, #[custom_default(...)] for arrays larger than 32, and mem::zeroed() for unions. I also got s! dispatching off a #[skip_derive_default] marker. Tested it on a plain struct, the big arrays, a union, and a struct containing a union, all passing.

    Two questions. Is zeroing unions ok when there's no custom_default? Seems right for the plain C unions but wanted to check. Also, do you want Default on everything by default eventually, or keep it opt-in per struct? Can't really flip it on globally without breaking every big-array struct until they're annotated, so I've got it opt-in for now.

    Only limitation is that custom_default has to be the first attribute on a field since the macro matches literally. Doesn't seem like it'd be a problem, but worth mentioning.

  5. tgross35 commented on Jun 20, 2026

    @tgross35
    MemberAuthor

    Mind if I take this? I messed around with a prototype impl_default! that generates the impl field by field. Default::default() for normal fields, #[custom_default(...)] for arrays larger than 32, and mem::zeroed() for unions. I also got s! dispatching off a #[skip_derive_default] marker. Tested it on a plain struct, the big arrays, a union, and a struct containing a union, all passing.

    Help would be very welcome! Especially if you have it working already, that's amazing.

    Two questions. Is zeroing unions ok when there's no custom_default? Seems right for the plain C unions but wanted to check.

    In general using mem::zeroed should be okay for all libc types, and is probably actually preferable for unions so we don't need to remember what the largest field is. But since this operation still requires asserting that zeros are a valid bitpattern, I'd like to have unsafe be needed somewhere in the macro.

    Could something like this work in the macro?

    #[unsafe(default_via_zeroed)]
    union bar {
        a: i32,
        b: f32,
    }

    I'd recommend doing that later though, and just starting with #[custom_default(unsafe { mem::zeroed<some_union>() })] the few places it's needed.

    Also, do you want Default on everything by default eventually, or keep it opt-in per struct? Can't really flip it on globally without breaking every big-array struct until they're annotated, so I've got it opt-in for now.

    Eventually on by default is the plan, but starting small sounds great. Could you do this by introducing a s_with_default! macro so we can do whole blocks?

    Only limitation is that custom_default has to be the first attribute on a field since the macro matches literally. Doesn't seem like it'd be a problem, but worth mentioning.

    That's pretty much always been my experience writing these kind of hacky macros, it's not worth the complexity to allow anything default.


    You might have this already but make sure you have some unit tests. Basically just a dummy s! invocation then pass the type to a fn assert_impls_default<T: Default>() {}.

  6. tgross35 commented on Jun 20, 2026

    @tgross35
    MemberAuthor

    In case it helps, here's one such macro I wrote for a syn-like crate. That one has extra complexity because it does different things with fields before and after #[group], but it's pretty nicely organized and commented https://gist.github.com/tgross35/b4c87129601ff336b6efe325c4dd5771.

  7. telcharr commented on Jun 26, 2026

    @telcharr
    Contributor

    Thanks for the detailed feedback! This is all very helpful. I'll get a PR up with the macro shortly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    E-help-wantedCall for participation: Help is requested to fix this issue.E-mediumE-medium Call for participation: Medium difficulty. Experience needed to fix: Intermediate.

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions