Then I noticed people were using them, preventing me from making changes I wanted to make. So I created a pseudo-"private" facility using macros, where people had to write FOOLIB_PRIVATE(var) to get at the internal var.
Then I noticed (I kid you not) people started writing FOOLIB_PRIVATE(var) in their own code. And I can't entirely blame them, they were trying to get something done, and they felt it was the fastest way to do it.
After this experience, I consider it an absolute requirement to have a real "private" struct member facility in a language.
I respect Andrew and I think he's done a hell of a job with Zig. I also understand the concern with the Java precedent and lots of wordy getters/setters around trivial variables. But I feel like Rust (and even C++) is a great counterexample that private struct variables can be done in a reasonable way. Most of the time there's no need to have getters/setters for every individual struct member.
You can't blame them, but they can't blame you if you break their code.
If you hesitate changing it because you worry about users using it anyway you are hurting the fraction of your users who are not using it.
Everyone is brainstorming ways to work around Zig's lack of "private". But nobody has a good answer for why Zig can't just add "private" to the language. If we agree that users shouldn't touch the private variables, why not just have the language enforce it?
Companies with monorepos could easily just ban the annotation. OSS projects could easily close any user complaint if the repro requires the annotation.
This seems like a great compromise to me. It would let you unambiguously mark which parts of the api are private, in a machine checkable way, which is undoubtedly better than putting it into comments. But it would offer an escape hatch for people who don’t mind voiding their warranty.
If they want to ignore the API contract then that's the right response. The maintainer chose one thing to preserve their ability to provide non-breaking updates. The user doesn't care about that, now it's on them to maintain that code which they're sinking their probes into.
Thing is, I don't have an opinion about what users should do. That's entirely up to them and the trade offs they make in their contexts. There are scenarios where you might want to access a private field.
But it's also a question about simplicity, adding private to the language makes it bigger without imo contributing anything of practical value that can't be achieved with convention.
This way users can get to them if they really need to, say for a crucial bug fix, but they're also clearly an implementation detail so you're free to change it without users getting surprised when things break etc.
If it’s in an internal monorepo, this should be super easy to fix using grep.
Honestly it sounds like a great opportunity to improve your API. If people are going out of their way to access something that you consider private, it’s probably because your public APIs aren’t covering some use case that people care about. That or you need better documentation. Sometimes even a comment helps:
int _foo; // private. See getFoo() to read.
I get that it’s annoying, but finding and fixing internal code like this should be a 15 minute job.I think that's the wrong take to have. Life is much easier when you accept the reality of a world where people will do whatever they want with what you give them.
C++ has private, and so what? I've seen #define private public or even -Dprivate=public, I've seen classes with private implementation detail reimplemented with another name and all fields public & then casted, I've seen accessing types as char arrays and binary operations to circumvent this, I've seen accessing the process raw memory pages. If someone other than you can call the code, it's not yours anymore to decide what can be done with it.
What you don't owe anyone is the guarantee of things working if people stray from the happy path you outline - they want help after going astray, give them your hourly rate on fixing their mistakes.
> You cannot reasonably form API contracts (...) unless you can hide the internal representation.
Yes you can, by communicating the intended use can be made with comments/docstrings, examples etc.
One thing I learned from the Clojure world, is to have a separate namespace/package or just section of code, that represents an API that is well documented, nice to use and more importantly stable. That's really all that is needed.
(Also, there are cases where you actually need to use a thing in a way that was not intended. That obviously comes with risk, but when you need it, you're _extremely_ glad that you can.)