Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add private set property modifier #4206

Open
tilucasoli opened this issue Dec 13, 2024 · 7 comments
Open

Add private set property modifier #4206

tilucasoli opened this issue Dec 13, 2024 · 7 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@tilucasoli
Copy link

tilucasoli commented Dec 13, 2024

Introduce a property-level modifier similar to Swift’s private(set) functionality. This would allow a class to expose a property for reading externally while restricting write access.

Motivation

In Swift, a common pattern is to declare properties as public private(set), which makes them publicly readable but only privately (or internally) writable. However, Dart does not have a similar feature, so we have to create a private variable along with a getter to achieve the same functionality.

Swift Example:

public class SomeClass {
    public private(set) var count: Int = 0
    
    public func increment() {
        count += 1
    }
}

Dart Example:

class SomeClass {
  int _count = 0;
  
  int get count => _count;
  
  void increment() {
    _count++;
  }
}

Proposed Solution

_set

class SomeClass {
  // Publicly readable, privately writable
   _set int count = 0;

  void increment() {
    count += 1; // Allowed
  }
}

// Outside SomeClass
void main() {
  final obj = SomeClass();
  print(obj.count); // Allowed
  // obj.count = 5; // Not allowed: write access is private
}

@nonVisibleSet

Alternatively, a annotation-based could be another option.

class SomeClass {
  @nonVisibleSet
  int count = 0; // read from anywhere, write only from within this class
}
@tilucasoli tilucasoli added the feature Proposed language feature that solves one or more problems label Dec 13, 2024
@lrhn
Copy link
Member

lrhn commented Dec 13, 2024

I've suggested something like that before. Can't find it right now.
The idea was to have bundled "property" declarations where you could declare implicit getter and/or setter for a variable independently, and also choose their names.

Strawman syntax:

int _value {
  get value;
  set;
};

This block states that there is a getter named value and a setter named _value for a variable which is itself named _value.(The name of a variable can only be used in one place: Constructor initialization, as this._value or _value = expr. Any other reference to the variable is a reference to a getter or setter, but initialization refers to the underlying storage location itself.)

By allowing you to specify an alternative name for a getter or setter, you can end up with default getters and setters for a variable with different names, including one being private.

Another way to write the same thing might be

int value { set _value; };

because you can't omit a getter, so omitting it is equivalent to get;, which is short for get value;.
You only need to say whether there is a setter, and then you can give it a non-standard name.

(Annotations are not an option. An annotation cannot change language behavior, so @nonVisibleSet int count = 0; would be visible to the language. The analyzer may warn if you access it, but you can // ignore: that if you want to.)

@tilucasoli
Copy link
Author

I like this sintax, for me it works

int a { set _a; }

What if I set an initial value for 'a'?

It would be something like this?

int a = 1 { set _a; }

@tatumizer
Copy link

For each verb in {get, set}, there are 4 possibilities:

  • omit it
  • write only verb
  • write verb followed by a _name (with the underscore)
  • write verb followed by a name (with no underscore)

Each of those has to be translated into the human language while reading the program (16 combinations in total).
That's not all. The declared name also comes in 2 colors: int _a and int a, so this has to be taken into account while figuring out the overall meaning.
It's complicated 😄

@tatumizer
Copy link

tatumizer commented Dec 18, 2024

Please consider a different syntax (C#-inspired)

int _a via a { get; set; } // "via" or something to the same effect

which is a shorthand for

int _a;
int get a => _a;
set a(int value) => _a = value;

In C#, there's also an "init" setter to be used in the constructor; in dart, we can use its variant to provide a default value for the constructor parameter, e.g.

final int x = 0; // looks bad. What does it mean? We want to set it in a constructor through an optional parameter.  
final int x { init = 0; } // better, the default for an optional parameter is 0

@lrhn
Copy link
Member

lrhn commented Dec 18, 2024

This syntax is C++ inspired.
Personally I don't like that via (not even named as), and think

int _a {get a; set a;}

reads better. And it allowed the getter and setter to have different names if you want it.

It does mean you have to say the name twice, but only if both are different from the storage name, and unlike C# you don't need to have a private storage to support public getters and setters, so you can always make one of them the same name as the field. The above could just be int a {get; set!}, there is no easy to tell the difference.

@tatumizer
Copy link

tatumizer commented Dec 18, 2024

int _a {get a;}

doesn't tell you there's no setter called "a". There can be an old-style setter set a(int value) => _a = value;
In contrast, the expression int _a as a {get;} kind of "reserves" a, so you can't write a separate setter called a.
Compare with your syntax for public name: if you declare int a {get;} you cannot define a separate setter for a (the compiler will complain about the duplicate name). The syntax int _a as a {get';} is trying to achieve the same for a private _a.

@tatumizer
Copy link

tatumizer commented Dec 19, 2024

One interesting possibility is that if something is declared with the syntax int a {get;} then it becomes a final variable, and in addition to this - a "stable" one, because the compiler will disallow creating a setter in subclasses. (We can interpret the syntax {get;} as saying there's no setter, so neither the class nor the subclass would be allowed to create one). @lrhn, may it work?
(the main argument here is that get has no body, so it can't return different results on different invocations. If the subclass wants to override, it would be required to use the same syntax int a {get;).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests

3 participants