Will Mojo support private struct members, or is that discussion settled as part of the let discussion?
I believe the underscore convention of Python is misplaced in a strongly typed language like Mojo. Leading underscores make the code more difficult to read and write, and are easy to forget, like List.capacity, which is missing an underscore, and whose mutation quickly leads to a segfault. It also makes it necessary to implement get_name or __getattr__ methods to prevent library users from performing unintended mutations.
I propose the following syntax, but have no strong opinion here.
struct OptInUsingRead:
var public: Int # same as ref var public: Int
read var private: Int
This discussion has mostly been tabled. I personally want to have public/private, but I also think that the author of the final executable binary should get an override, so my suggestion in the previous discussion of this issue was to leave public/private until we have a reflection API where a developer can grab a struct member by name even when it is private, albeit with a lot of ceremony. Once that escape hatch exists, then many of the potential issues of public/private, such as library authors not exposing all relevant data or needing to access private members for unit tests, go away.
You can get a pointer to the object using that method, which invokes a whole host of UB, but if we had private fields that still wouldn’t give you a pointer to the field, you’d need __offset_of() or similar since Mojo technically doesn’t define a layout for structs.
Ah, sorry for the confusion. In my head private members in Mojo would be readable, i.e. accessable as an immutable references. Would having readable members be an option? Then the escape hatch I mentioned should work.
Then we are back to using leading underscores everywhere for the “real” variables, but I agree that this works. Thanks for taking the time to reply!
I think a really simple and already available solution for a “read only” public API would be to introduce a “pattern” by using immutable references.
Example:
struct Incrementer:
"""A simple struct to demonstrate the use of a read-only public API in Mojo.
It has a method to increment a count and a read-only method to access the count.
"""
var _count: Int
fn increment(mut self):
self._count += 1
@always_inline
fn count(read self) -> ref [ImmutableAnyOrigin] Int:
return self._count
fn __init__(out self):
self._count = 0
fn main() raises:
var inc = Incrementer()
# Immutable reference to the count
ref count = inc.count()
# This will raise an error because count_ref is immutable
# count += 100 # error: count_ref is immutable
# Print the current count
print("Initial count:", count)
# Increment the count using the method
inc.increment()
# Print the updated count
print("Updated count:", count)
So the pattern is:
var _foo: T
for non public access and
fn foo(read self) -> ref [ImmutableAnyOrigin] T: return self._foo
for public read only access.
I think @always_inline will even remove the function indirection.
start with _ - module/class/struct private (or unused which could be seen as the ultimate private)
start with __ - reserved for language usage like __str__
The advantages embracing this naming convention in Mojo:
A compiler/linter rule warning/error can enforce this (possibly adding #lint-disable rule as an escape hatch).
Similar to python, won’t alienate python user, or require them to learn more.
Providing an escape hatch to access private member of some library who didn’t expose/implemented some feature. You can submit a PR and wait for review and publish which will take time (or never), but in the meantime at least you can use it.
This syntax is very short, and doesn’t distract you from the logic, unlike JAVA verbose public static int main, adding more keywords like private/public will just add syntactic noise.
In general I think:
The language should empower the programmer with abilities, and provide automatic checks and warning, but there should always be a way to override these, allowing “unsafe behavior”.
Be like python unless there is a big reason to deviate, which I don’t see one here since you can get the same benefits by enforcing the python naming convention.
**BTW private/public access is orthogonal to mutability since you can have all options private read, private mut, public read, public mut.
The problem is that many of us have seen people use that “escape hatch” when they really, really shouldn’t have. I personally think that escape hatches that let you break encapsulation should be verbose, easy to find when auditing the codebase, and something that is going to be learned later on when learning a language. Right now, python’s way to override a private variable is the same as accessing a public variable, which makes it very, very low effort. In a systems language like mojo, messing with the internals of objects can violate safety guarentees and lead to UB, especially for types encapsulating unsafe behavior such as a Mutex. We could make accessing a member that starts with an _ a hard error, and keep the python convention, but I personally think that, being a systems language, Mojo needs to be able to provide stronger encapsulation than Python can, at least until the developer is at a level of experience where they would, for example, know about the static reflection APIs and be able to use them along with a trait to add a getter to the type.
Different python projects also have different rules for what scope accessing a private variable is allowed in. Many only allow it inside of the class, but some do module scoping, or library scoping. If we treat it as a hard error for _ variables to be accessed outside of some scope, we may break extension libraries, or just normal libraries.
Then force a linting disable/decorator above the expression something like:
# in python pylint for example
# pylint: disable=protected-access
module._some_private_member
# in mojo maybe something like:
@disable_rule("protected-access")
module._some_private_member
I don’t believe in making things harder just to discourage people from using it. But I do agree it is important to be able to find all cases easily.
I think we should allow an option for projects to prevent using this @disable_rule for the entire project in the project.toml file, so projects could forbid it entirely.
The advantage of mojo is it’s a new language so it can pick the convention.
The transformation tool from python to mojo might be able to handle the differences.