简体   繁体   中英

Updating public fields of Rust structs which have private fields

I have a struct Foo which represents an external serialization format. Foo has dozens of fields, and more are added all the time. Happily, all new fields are guaranteed to have sensible default values.

Rust has a nice syntax for creating a struct using default values and then updating a few selected values:

Foo {
  bar: true,
  ..Default::default()
} 

Similarly, we can represent the idea of "this struct may have more fields in a future version" using a private field of type PhantomData .

But if we combine these two idioms, we get an error:

use std::default::Default;

mod F {
    use std::default::Default;
    use std::marker::PhantomData;

    pub struct Foo {
        pub bar: bool,
        phantom: PhantomData<()>,
    }

    impl Default for Foo {
        fn default() -> Foo {
            Foo {
                bar: false,
                phantom: PhantomData,
            }
        }
    }
}

fn main() {
    F::Foo {
        bar: true,
        ..Default::default()
    };
}

This gives us the error:

error: field `phantom` of struct `F::Foo` is private [--explain E0451]
  --> <anon>:23:5
   |>
23 |>     F::Foo {
   |>     ^

Logically, I would argue that this should work, because we're only updating public fields, and it would be useful idiom. The alternative is to support something like:

Foo::new()
  .set_bar(true)

...which will get tedious with dozens of fields.

How can I work around this problem?

The default field syntax doesn't work because you're still creating a new instance (even if you're trying to take some of the field values from another object).

The alternative is to support something like:

 Foo::new() .set_bar(true) 

...which will get tedious with dozens of fields.

I'm not sure that even with many fields, this:

Foo::new()
   .set_bar(true)
   .set_foo(17)
   .set_splat("Boing")

is significantly more tedious than:

Foo {
   bar: true,
   foo: 17,
   splat: "Boing",
   ..Foo::default()
}

Alternatively, you could separate out the public fields into their own type:

pub struct FooPub {
    pub bar: bool,
    // other pub fields
}

pub struct Foo {
    pub bar: bool,
    // other pub fields
    // alternatively, group them: pub public: FooPub,

    foo: u64,
}

impl Foo {
    pub fn new(init: FooPub) {
        Foo {
            bar: init.bar,
            // other pub fields
            // alternative: public: init

            // private fields
            foo: 17u64,
        }
    }
}

You'd then call it as:

Foo::new(FooPub{ bar: true })

or add a fn FooPub::default() to let you default some of the fields:

Foo::new(FooPub{ bar: true, ..FooPub::default()})

Rename phantom to __phantom , make it public and #[doc(hidden)] .

use std::default::Default;

mod foo {
    use std::default::Default;
    use std::marker::PhantomData;

    pub struct Foo {
        pub bar: bool,

        // We make this public but hide it from the docs, making
        // it private by convention.  If you use this, your
        // program may break even when semver otherwise says it
        // shouldn't.
        #[doc(hidden)]
        pub _phantom: PhantomData<()>,
    }

    impl Default for Foo {
        fn default() -> Foo {
            Foo {
                bar: false,
                _phantom: PhantomData,
            }
        }
    }
}

fn main() {
    foo::Foo {
        bar: true,
        ..Default::default()
    };
}

This is a not so uncommon pattern, live example: std::io::ErrorKind::__Nonexhaustive .

Sure, users won't have any warning or anything if they choose to use a __named field anyway, but the __ makes the intent pretty clear. If a warning is required, #[deprecated] could be used.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM