Remix.run Logo
smj-edison 8 hours ago

I feel like encapsulation and composition are in strong tension, and this is one place where it boils over.

I've written a decent bit of Rust, and am currently messing around with Zig. So the comparison is pretty fresh on my mind:

In Rust, you can have private fields. In Zig all fields are public. The consequences are pretty well shown with how they print structs: In Rust, you derive Debug, which is a macro that implements the Debug trait at the definition site. In Zig, the printing function uses reflection to enumerate the provided struct's fields, and creates a print string based on that. So Rust has the display logic at the definition site, while Zig has the logic at the call site.

It's similar with hash maps: in Rust you derive/implement the Hash and PartialEq trait, in Zig you provide the hash and eq function at the call site.

Each one has pretty stark downsides: Zig - since everything is public, you can't guarantee that your invariants are valid. Anyone can mess around with your internals. Rust - once a field is private (which is the convention), nobody else can mess with the internals. This means outside modules can't access internal state, so if the API is bad, you're pretty screwed.

Honestly, I'm not sure if there is a way to resolve this tension.

EDIT: one more thought: Zig vs Rust also shows up with how object destruction is handled. In Rust you implement a Drop trait, so each object can only have one way to be destroyed. In Zig you use defer/errdefer, so you can choose what type of destructor runs, but this also means you can mess up destruction in subtle ways.

antonvs 3 hours ago | parent [-]

> so if the API is bad, you're pretty screwed.

Is this really that big a downside? It encourages good APIs.

The alternative of everything being public is the kind of feature that quickly becomes a big disadvantage in larger systems and teams, where saying “just don’t footgun yourself” is not a viable strategy. If there’s a workaround to achieve some goal, people will use it, and you end up with an unmaintainable mess. It’s why languages whose names start with C feature so prominently on CVE lists.

HdS84 2 hours ago | parent [-]

There are always corner cases where you might need to do something differently. I had three memorable cases in my career: 1. Python 2.6x had a a stdlib bug where windows event logging did crash the process when the user had some rights set differently. Fix submitted but for the meantime we simply overwrote the private function and could ship. 2. Also python: scikit-learn had a primitive "print everything" strategy, but we need to get it into a logging framework. We overwrote their print wrapper and could ship. 3. In C#, a third party lib insisted on dumping a result to a file. We used reflection to get that as a stream.

All three are not ideal - but I think having escape hatches is important. I also think private/public is overrated. Having it as a signal is ok. Forbidding access to privates is too strong.