Structs with public fields are brittle

Any struct with only public fields or no fields at all can be pattern matched and instantiated wherever it is visible. This makes structs brittle, as the simple addition of a private field (implementation detail) breaks any third-party code that (inadvertently) instantiates or pattern matches on the struct. One would need to add a useless dummy private field to such struct to prevent this explicitly.

struct Foo {
    pub value: i32,
    _dummy: u8,
}

This is awkward and counter the Rust philosophy (as I understand it) that an operation is only publicly allowed if it has been made public explicitly. Similar to how structs are private by default, public instantiation and pattern matching of structs should not allowed by default. Only if the programmer explicitly states that (s)he wants to allow public instantiation and pattern matching of the struct should it be allowed. And it is an error if the programmer later adds a private field to such a struct. This would make the intended use of the struct a lot more explicit and prevents the programmer from inadvertently making breaking changes.

Has this been discussed before? What's the take on this? It would be a breaking change to Rust if this is dealt with after the 1.0 release. I could only find the slightly related issues #22045 and #22949 in the Rust repo.

2 Likes

It's actually worse than that: the addition of any field, public or private breaks pattern matching. This has been discussed. Currently, the standard library works around this adding dummy values (you can use a zero-sized dummy value for efficiency). One idea was to use ellipsis:

struct Foo {
  pub a: i32,
  pub b: i32,
  ..
}

This is the inverse of let Foo { a, b, .. } = some_foo;. The same would apply to enums:

enum Bar {
  A, B, ..
}

See the extensible enums RFC: https://github.com/rust-lang/rfcs/pull/757. IIRC, there was also a discussion on the internals forum but I can't find it.

2 Likes

I see. It is to be expected that the addition of a public field is a breaking change, but private fields should be implementation details. Adding a private field shouldn't be a breaking change.

I read the issue you linked, it is mostly about extensible enums. For structs I think the ellipsis thing is the wrong way round. You wouldn't want brittle structs to be the default. I added a comment there.

Are you referring to adding another private field or “the first” private field? In the latter case, you're changing the struct from being constructible with a struct literal to not, so it's a big change.

Yes, adding a private field to a struct is generally a minor change that has no impact whatsoever on existing public consumers of the struct, except if the added field happens to be the first private field, then it's a major breaking change. This is a hidden trap.

I propose to treat every struct as if it contains private fields (even empty structs and all-public structs), unless explicitly stated (opt-in) that the struct will never contain private fields. Everything else (destructuring, FRU, instantiation) follows from that.

1 Like

Is it that serious of a "trap", though? How many structs want to mix pub/priv fields?

It's an opt-out from stability instead of an opt-in.