Proposal: Access Control via `@private` Decorator in Mojo

Proposal: Access Control via @private Decorator in Mojo

Executive Summary

As Mojo refines its visibility model, we propose adopting an explicit decorator approach (e.g., @private, @public) rather than keyword modifiers (e.g., private def).

This approach preserves visual code skimmability, avoids grammar/parser bloat, aligns with Python’s design philosophy, and offers an incredibly simple path to deployment: initially implementing @private as a direct alias for @doc_hidden, requiring virtually zero engineering resources.

Key Arguments

1. Unmatched Consistency and Left-Margin Skimmability

In Mojo, function declarations start predictably with def or async def. This creates a clean visual anchor along the left edge when scanning a file.

Prefix modifiers break this natural rhythm:

Code-Snippet

# With keyword modifiers (Scattered left edge)
public def connect():
    ...

private def authenticate():
    ...

async def fetch_data():
    ...

With decorators, the core declaration keyword (def, async def) remains grounded and easy to locate at a glance:

Code-Snippet

# With @private decorator (Consistent visual anchor)
def connect():
    ...

@private
def authenticate():
    ...

async def fetch_data():
    ...

Because visibility is metadata about a function rather than the fundamental act of defining it, keeping it as a decorator keeps function signatures visually consistent.

2. Zero Engineering Resources: The @doc_hidden Alias Strategy

The most elegant aspect of this proposal is its path to immediate adoption. Mojo already supports @doc_hidden to suppress symbols from exported documentation and APIs.

Initially, @private can simply act as a syntactic alias or attribute wrapper for @doc_hidden:

  • No new compiler passes needed: It leverages existing attribute-handling logic right out of the box.

  • Instant utility: Developers get the ergonomic syntax they want immediately, while true scope-enforcement checks can be built under the hood later as the type checker evolves.

It turns what could be a massive language feature implementation into a zero-cost quick win.

3. Zero Parser Overhead (No Keywords or Lookahead)

Introducing private as a prefix modifier creates unnecessary parser complexity:

  • Keyword Pollution: Making private a reserved keyword prevents developers from using it as an identifier (e.g., var private = True).

  • Parser Lookahead: To avoid making it a strict keyword, the parser would need context-sensitive lookahead to distinguish whether private is a variable name or a visibility modifier preceding def or struct.

Because decorators already use the @ sigil, @private hooks directly into Mojo’s existing grammar with zero parser changes.

4. Preserving the Python DNA

Mojo stays true to Python by prioritizing explicit structure over keyword clutter. Python shuns C++/Java-style access keywords in favor of explicit decorators (@staticmethod, @classmethod, @property).

Using @private maintains explicit intent, fits naturally into the ecosystem, and keeps the code feeling like Mojo rather than a C family dialect.

Code Example Comparison

Recommended: Decorator Approach

Code-Snippet

struct Vault:
    var balance: Float64

    def __init__(out self, initial: Float64):
        self.balance = initial

    @private
    def _reconcile(mut self):
        # Internal accounting logic
        pass

    def deposit(mut self, amount: Float64):
        self._reconcile()
        self.balance += amount

Alternative: Prefix Keyword

Code-Snippet

struct Vault:
    var balance: Float64

    def __init__(out self, initial: Float64):
        self.balance = initial

    private def _reconcile(mut self):
        # Disrupted indentation anchor
        pass

    def deposit(mut self, amount: Float64):
        self._reconcile()
        self.balance += amount

Conclusion

The @private decorator keeps code easy to scan, protects the grammar from keyword bloat, respects Python’s design traditions, and can be shipped almost instantly by Aliasing @doc_hidden. Adopting decorator-based visibility is the cleanest, most pragmatic choice for Mojo.

This approach preserves visual code skimmability, avoids grammar/parser bloat, aligns with Python’s design philosophy, and offers an incredibly simple path to deployment: initially implementing @private as a direct alias for @doc_hidden, requiring virtually zero engineering resources.

When we implement accessibility modifiers, I expect us to make them actually enforce said access, now that we have escape hatches via reflection for when you want to strongly disagree with the author. This is because in most cases when something is private it means you really shouldn’t be touching it.

doc_hidden also doesn’t make the LSP ignore things, which means engineering work would be required, and it would still require grammar + parser modification to add those decorators due to how Mojo’s decorators currently work.

This design space is still open, but any path to implementing it does require a decent amount of engineering work and consideration, which is why it has been de-prioritized for 1.0 since it’s a purely additive feature.

I‘m not a big fan of strict “private” annotations. It makes a language less flexible and more complex:

  • “private” for what code? Outside the struct? Outside the module (file)? Outside the package?
  • Can extensions access “private” members or methods?
  • Is something “private” accessible through reflection?
  • when to use “private” and when “_foo”?

If “private” code is still accessible through reflection this would only make the language more complex without keeping it more safe. Especially in the time of LLM code generation it would be easy and effortless to work around any “private” limitations.

I like “private” or “unsafe” naming conventions much more and it is more in line with Python: “_foo” and “unsafe_foo”. It is obvious and easy to see that those fields and methods should not be used unless you exactly know what you’re doing and accept the risks that are associated with it.

This makes it more easy to:

  • experiment in novel ways with existing code
  • work around limitations that a code provider did not foresee.

It would still be easy to write “safe” code by not using any private fields or methods. This could even be enforced with a linter on a per project basis.

At least from a performance and flexibility perspective strict “private” annotations are not needed.

If strict privacy is considered I agree that this needs a lot of thinking to get to a good design that is true to Mojos mission.

I would prefer the simple naming conventions:
* No _ for public
*_somevar for private

plus a compiler flag to be strict about accessing private variables or merely warn

Those scenarios will need to be worked out. Extensions outside of the scope that can normally access those members likely will not have access to the private members.

var _foo gets replaced by private var foo or however we decide to spell that.

If “private” code is still accessible through reflection this would only make the language more complex without keeping it more safe. Especially in the time of LLM code generation it would be easy and effortless to work around any “private” limitations.

The point is that seeing a big pile of reflection code to access a private member variable is a big red flag for code review since it jumps out like a sore thumb. If you can justify it to the reviewer, then it’s fine, but it’s there as a hack and we can make file lookups return Optional so that you are forced to handle the case where the struct has renamed that member.

The reason that a private annotation is desirable is because of encapsulation and API stability. It’s very easy to accidently break encapsulation, especially with agents involved. LLMs know what private means because Java exists, so even they will more or less properly handle the concept.

I would prefer the simple naming conventions:
* No _ for public
*_somevar for private

This is more confusing for non-python programmers. In general, the concept of a “private variable” is fairly far reaching across software development, but making the way to spell that not include the word “private” or some shortening like “priv” seems like it would cause confusion.

plus a compiler flag to be strict about accessing private variables or merely warn

I don’t think this is a good idea. There will very quickly be libraries that only compile with this flag set to “warn” mode and it would create a culture of ignoring warnings.

I am not currently using Mojo because it is still maturing, but adoption will ultimately depend on whether the language gives experienced developers the ability to express the design decisions they actually need in a given context / development phase. If the language enforces constraints that conflict with my engineering judgment, I will simply choose a different tool.

I understand the value of encapsulation and API stability, especially in large ecosystems and with generated code. But there is a difference between preventing accidental misuse and blocking intentional use. The language should make good practices easy and visible, but it should not remove the ability of developers to make informed trade-offs.

I think LLMs know what the “_foo” convention means in Python too and I see no obvious reason to deviate from Python in this respect. Maybe it is even easier to review the usage of “_foo” than a pile of reflection code. But I agree that having an option to enforce strict privacy can be desirable too.