Skip to content

Proposal: Private Fields #9909

@ghost

Description

Introduction

Currently, function and variable declarations are private by default and are made externally visible with the pub modifier. However, data fields are always public and there is no way to restrict their visibility. Apparently, field access control was never really working in Zig (#569) and was officially removed in #2059. Which is somewhat surprising, since no other modern language I can think of (Java, C++, C#, D, Rust, Go, ...) fails to provide this feature.

The lack of private data makes it impossible to do proper encapsulation / implementation hiding, which is widely considered to be a basic software engineering technique. In particular:

  • Types can hide some of their methods and constants, but always expose their internal structure. This leaves them open to accidental (or bad-practical) corruption. The compiler will not complain if the .capacity of an ArrayList is overwritten.
  • Documentation value is lost. Whether or not a field is intended for direct access can only be communicated through code comments.
  • Auditing code becomes more difficult, because there are more opportunities to violate constraints and invariants.
  • Implementing opaque handles is nearly impossible without additional builtins: Introduce @OpaqueHandle() builtin #9859, #1595 (comment).

Possible objections

No official reason was given in #2059, but two possible objections to private fields come to mind:

  1. Visibility control is more useful for decls than for fields, because invisible decls become completely inaccessible, while fields can always be messed with through pointers.
  2. This will lead to over-encapsulation and getter-setter boilerplate (#2974 (comment)). Sometimes it's just better to reach in and do things directly.

Concerning 1, I'd say that protection does not have to be perfect to be useful. Preventing accidental and semi-deliberate messing is valuable by itself, not to mention the documentation value.

2 may be a real concern, or maybe not. The same argument can be made about private decls, but a proposal to override the visibility of methods at the call site (#8779) was recently met with a resounding rejection -- even though there are certainly cases where it is both reasonable and safe to call private methods. In addition, Zig is increasingly making legal but potentially buggy patterns into hard errors, somtimes controversially (e.g. unused variables). The lack of basic implementation hiding is not consistent with this safety-first attitude, IMHO.

Syntax

The simplest option is to adopt the same default as with decls: file-level private by default and externally visible with pub. If this clashes with data-oriented style, the opposite default can be chosen, but that wold require the introduction of a private keyword. Yet another possibility is to make field visibility struct-level, e.g. with opaque struct {...}.

I don't really have a strong opinion here.

Open questions

  • Is there a case for field-level access control in unions?

Update 1: As a side benefit, it may be possible to remove the opaque {} type from the language, since it is rarely used and can be simulated with struct { private ptr: usize }.

Update 2: I wouldn't mind adding an escape hatch like @privateField(object, "name") for cases where you need to use a particular library, but find the API too locked-down. Some discussion of this is here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    proposalThis issue suggests modifications. If it also has the "accepted" label then it is planned.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions