ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
https://ziglang.org
MIT License
34.71k stars 2.53k forks source link

remove pub syntax for struct/union fields #2059

Closed andrewrk closed 5 years ago

andrewrk commented 5 years ago

Right now Zig lets you put pub in front of a struct/union field and it does nothing. Originally I was planning on this doing something, but now I'm not sure that private struct fields should be a thing in Zig.

emekoi commented 5 years ago

is there any particular reason that you think that private fields shouldn't be a thing in zig?

hryx commented 5 years ago

I think this is a pretty important issue, and I'm somewhat torn. Here are the gnarliest bullet points for me.

Yay

Today, a struct initialization requires all fields to be set explicitly, either to a value or to undefined. This is fantastic: you can't forget a field, and if someone quietly breaks the API to a library you use, then your existing code will alert you with a compile error.

If some fields are public and some private, how will struct initialization work? It would be a big fat shame to lose the above benefit.

Nay

The change would mean there is no such thing as a private field. A pub fn of a struct communicates its API, but so do fields. So removing private fields means either:

  1. you are signalling to the user that all fields are meant to be read/written directly; or
  2. to uphold a properly encapsulated interface, your struct cannot have private state.

No. 1 violates "communicate intent precisely", and no. 2 is downright infeasible.

I wonder if there is some way to address this disadvantage with a yet-unexplored language feature.

emekoi commented 5 years ago

now that we have default field values are there any other reasons pub should be removed for fields?

distractedlambda commented 5 years ago

I'm actually partial to the idea of keeping everything public in Zig, but also establishing a firm naming convention for "internal" symbols (e.g. prefix with an _). If a symbol name is formatted as an internal symbol name, you assume that it's not a part of the stable API.

Obviously this does not outright stop someone from accessing private symbols and watching their program break when a dependency updates, but it clearly communicates the intent of the API author, as much as any other solution to this problem.

My experience with languages that formalize access control, is that they often can't express the kinds of "access groupings" I want for the API I'm writing. It's pretty often that you end up with a group of types that all have good reason to know the internal details of each other, but which export an API that shouldn't require any access to internal details from outside that group. Languages have tried "friend" declarations or "file private"/"module private" symbols to capture these kinds of patterns, and I've found those solutions awkward at best. You end up organizing your code around the language's access control system, instead of around the logical separation between components.

Maybe more importantly, I'm not sure how Zig would incorporate a good formalized access control system with its current model of package organization. The compiler has no semantic understanding of a hierarchical relationship between files.

ghost commented 5 years ago

@lucascharlesmyers

I'm actually partial to the idea of keeping everything public in Zig, but also establishing a firm naming convention for "internal" symbols (e.g. prefix with an _). If a symbol name is formatted as an internal symbol name, you assume that it's not a part of the stable API.

I have also thought about that, and maybe a better way to provide encapsulation is through interfaces anyway? That is, no encapsulation within a project, but when importing a 3rd party project or library, you only see what the "provided interfaces" of the library expose. In summary, encapsulation would be a package manager responsibility, not a language responsibility. Of course, naming conventions can still be used like you say.

distractedlambda commented 5 years ago

@user00e00 Swift uses this a lot (in addition to public and private and fileprivate, there's an internal access level that is accessible by everything in the same module). Things get dicey in that language because access control is intertwined with ABI stability and linking, but as long as Zig considers extern fn the only way to get a stable ABI, I think having "package-private" symbols would be reasonably ergonomic.

I think there are certain times where it's pragmatic to be able to access the internals of an external package you're using (e.g. working around a bug / an API deficiency), but I can see reasonable counter-arguments to that. Namely, if you allow somebody to work around deficiencies in an external library they're using, you might be making it less likely that the problem actually gets fixed upstream. If you don't allow messing with the private symbols of external dependencies, you at the very least force people to fork dependencies they want to alter, and forks are much more likely to turn into pull requests than workarounds at usage sites.

bb010g commented 5 years ago

How's this work with @import returning normal structs?

distractedlambda commented 5 years ago

@bb010g If I understand what you're asking, I would think it would be sufficient for @import to "prune" structs according to some chosen rules. e.g. @importing a file gives you the full struct, private members and all, but @importing a package excludes private members from the returned struct. It might be prudent to then give those usage cases and semantics different names for clarity, e.g. @import and @importPackage, but that's a separate discussion.

distractedlambda commented 5 years ago

Also, thinking about it more, I'm strongly in favor of indicating access level with identifier names, i.e. have it baked into the language grammar that names following a particular pattern are to be treated as private/internal identifiers, in place of access-control keywords. Here's my reasoning:

  1. You're not always in a situation where you can "Go To Definition" on a symbol name you're unfamiliar with, and in those cases being able to know something about a symbol by its name is valuable. Requiring e.g. a special prefix on all private symbols, like _symbol, has very little cost when writing code, and can improve readability.
  2. It's been my experience that even in languages with access control keywords, patterns develop for naming "internal" or "implementation" symbols to avoid name conflicts with public symbols of what would otherwise be the same name. These patterns vary from developer to developer and codebase to codebase, which can make it harder to read code you're not familiar with. By forcing a naming convention at the language level, and therefore preventing private symbols from ever having the same names as public ones, we eliminate this source of inconsistency.
jakwings commented 4 years ago

Is there a summary of the reasons for the removal? What is the recommended way to mark fields that are subject to change without warnings?

tmccombs commented 3 years ago

In C it is a pretty common pattern to declare a struct without any fields in the .h file so that the struct is opaque to client code, and it has to use the api. How would you do that in zig?

daurnimator commented 3 years ago

In C it is a pretty common pattern to declare a struct without any fields in the .h file so that the struct is opaque to client code, and it has to use the api. How would you do that in zig?

https://ziglang.org/documentation/master/#opaque

However your question is off topic for this issue; consider asking in one of the community discussion areas: https://github.com/ziglang/zig/wiki/Community