Allow FRU syntax on non_exhaustive structs

FRU syntax does not work across crate boundaries on non_exhaustive structs. This hampers ergonomics, especially when it comes to things like the Default trait.

For example:

// Crate 1
#[non_exhaustive]
pub struct Options {
    pub foo: String,
    pub bar: String,
}

impl Default for Options {
    fn default() -> Self {
        Options { foo: "foo".to_string(), bar: "bar".to_string() }
    }
}

// Crate 2 (does not compile!)
fn main() {
    let _options = Options { bar: "baz".to_string(), ..Default::default() };
}

Back-reference: https://github.com/rust-lang/rfcs/pull/2008/files/3d568896c29e3071a7582533418e02c6f32fa085#r122049087

@Manishearth

See Pre-RFC: Relaxed #[non_exhaustive] structs for the existing thread on this topic.

1 Like

There was an interesting macro in that thread:

Click to see
#[doc(hidden)] pub use ::core;

#[macro_export]
macro_rules! FRU {(
    $Struct:path {
        $(
            $field_name:ident : $value:expr,
        )*
        .. $($initial:expr)?
    }
) => ({
    let it = $crate::core::default::Default::default();
    $(
        let () = it;
        let it = $initial;
    )?
    let mut it = it;
    let $Struct { $($field_name: _,)* .. } = it; // ensure any DerefMut shenanigans are shadowed
    $(
        it.$field_name = $value;
    )*
    it
})}

which allows writing:

// Crate 2
fn main ()
{
    let _options = FRU!(Options { bar: "baz".to_string(), .. /* default() */ });
}

There's plenty of historical discussions around how FRU doesn't work like people expect, which are the root of why it can't work today with structs with private fields.

It's a breaking change to change it, but personally I'd be interested in seeing someone take it up as an edition change...

2 Likes