Very interesting set of observations. As a C and C++ programmer, I am feeling more and more excited about Zig.
One thing that feels missing from Zig though is encapsulation. I don't believe that you can declare struct fields private, such that they can only be accessed by methods. This seems really important to me, not only as a technical means of enforcing invariants, or hiding internal-only implementation details that may change, but even just as a communication of intent. There is a big difference between "you are invited to read/write this directly" and "please don't read/write this."
C doesn't have this, true, but at least in C you can put internal-only structs in a .c file instead of .h, making the members effectively invisible to clients. Does Zig have anything comparable?
If you want to communicate intent, put an underscore in the field name, like it's being done in Python. `field` is "you are invited to read/write this directly", `_field` is "please don't read/write this."
Even in a lot of languages that do have "private", it's still pretty consent-based. Java and C#, for example, don't prevent you from mucking with private members, they just make it inconvenient to do so.
I used to be suspicious of the Python approach, because it doesn't even pretend it's enforced by anything but the honor system. But I've discovered that, in practice, my Python-using colleagues are no less likely to respect `_field` than my Java-using colleagues are to respect `private field`.
> But I've discovered that, in practice, my Python-using colleagues are no less likely to respect `_field` than my Java-using colleagues are to respect `private field`.
That's a very interesting observation. You really have to go out of your way to access private fields in Java -- I can only recall about 3-5 instances of doing that in my 15+ years programming in Java. You have to look up a field by name, and call setAccessbile(true) on its reflection... and this is definitely going to raise eyebrows in code review. Some of the DI frameworks do it as a matter of course, but that's kind of a different thing...
(Now, package-private is a different matter. In that case you can[0], just put a class in the same package and access from there. I've done that... twice or so?)
What is your recollection in terms of doing the same thing in Python? Obviously, this is just going to be anecdata, but it could be kind of interesting.
[0] Modules change this a bit, but it doesn't seem modules are really a thing outside the standard library yet. (I'm programming in Scala these days, so haven't kept up with Java practices.)
I've mostly seen it done in situations that are roughly analogous to IoC containers. So, custom serde libraries that use reflection, testing utility code, object mapping tools, magic validators, stuff like that. Which, any sufficiently venerable enterprise Java application seems to have at least one or two of those knocking around the codebase.
FWIW, Java is also where I see stringly typed designs, too. I'm increasingly coming to fear that languages with more safety-oriented features aren't associated with safer practices because those features encourage safer design, so much as because they tend not to attract programmers with a swashbuckling attitude in the first place.
Right, for DI/IoC it seems pretty standard. (I think it's probably a historical accident, honestly. Reflection was really the tool to get constructor parameters and then someone figured, hey, why not just inject private values directy? It surely convenient at the time, but... lessons learned, I guess. I have no problem with compile-time DI via static reflection of e.g. constructors. It's a little more boilerplate, but worth it, IMO)
Definitely agree about "stringly" typed programming becoming an increasing issue in Java over the years, but that had more to do with over-use of instanceOf, etc., not so much actual strings (as in className).
> I'm increasingly coming to fear that languages with more safety-oriented features aren't associated with safer practices because those features encourage safer design, so much as because they tend not to attract programmers with a swashbuckling attitude in the first place.
For the life of me I cannot parse this sentence. Could you please rephrase or expound? Is there a missing negative somewhere, or...?
> I'm increasingly coming to fear that languages with more safety-oriented features are[] associated with safer practices [not] because those features encourage safer design, [but] because they tend not to attract programmers with a swashbuckling attitude in the first place.
Indeed. JavaScript also doesn't have private fields. But in practice using an undocumented field means "I'm familiar with the internals of this library and willing to check for breaking changes on every update".
There are lots of footguns in JS, but I've never seen this cause issues.
I bet it wouldn't be hard to write something in zig that enforces this. I think the compiler is going to start getting pluggable and when a package manager comes out I'm sure something like an opt-in check against this will become a thing.
I'm 90% in favor of this; the 10% comes from the few times where I've wished that a particular field was exported, and I know it would be safe to access, but now I need to open an issue or submit a patch, wait for it to be merged upstream, update the dependency version...
Give that Zig is a low-level language where "unsafe escape hatches" are the norm, it wouldn't surprise me if this ended up being an optional compile-time check rather than a mandatory one (as a sibling comment suggested).
I'm ok with an escape hatch. I'm even ok with a strong convention. My primary concern is that there is a clear contractual line between "reading/writing this is supported" and "you're on your own."
Yes this is an area where Zig fundamentally doesn't solve the same problems that C had. Zig can't check that you're not dangerously wrecking an invariant while Rust can do that, or in the worst case simply cause a program exit with a panic rather than random memory corruptions like Zig would cause.
One thing that feels missing from Zig though is encapsulation. I don't believe that you can declare struct fields private, such that they can only be accessed by methods. This seems really important to me, not only as a technical means of enforcing invariants, or hiding internal-only implementation details that may change, but even just as a communication of intent. There is a big difference between "you are invited to read/write this directly" and "please don't read/write this."
C doesn't have this, true, but at least in C you can put internal-only structs in a .c file instead of .h, making the members effectively invisible to clients. Does Zig have anything comparable?