Open lrhn opened 1 month ago
Protected members are non-virtual, non-interface methods. They can be shadowed by other protected or non-protected members. They become virtual only when made public by being given a interface signature, without introducing new implementation.
I don't really understand this paragraph. why can they be shadowed? and why aren't they virtual?
class A {
protected void protectedFunc(){
print('in A');
};
void init(){
protectedFunc();
}
}
class B extends A{
@override
protected void protectedFunc(){
print('in B');
}
}
B b;
b.init(); // -> should call B.protectedFunc() and not the one of A
A virtual method is late-bound, the actual implementation chosen based on the runtime type of the receiver it's called on. An interface method is basically the same in Dart. (Java has separate JVM instructions for interface invocations and virtual instance member invocations because they have separate interface
and class
declarations. It's all interfaces in Dart.)
Now, the question is whether this is the correct design. Your example would print in A
the way this is specified, because A
doesn't know that protectedFunc
is overridden in subclasses. But that doesn't seem necessary, or particularly useful.
If a call could be virtual, that is: a this.name
invocation would be virtual, and a super.name
is still not, then subclasses can override protected members (and must if the protected members is abstract, which now also makes sense).
That's actually much more useful. (Aka: Doh! Why didn't I see that.)
I'll see when I can find time do an update :)
I agree that it's a good idea if it prints B
, but that was not what I had specified. As specified, it would print A
.
So I fixed it. :wink: (And now nobody can see the first version any more, and laugh at its naivity. Hah!)
The title still says "non-virtual", but the second edition never mentions the word "virtual" again. In what sense are they non-virtual? It looks like their virtualness is the same as that of normal methods - they are just visible only to subclasses. If that's not the case, some examples could help.
virtual doesn't only mean visible into subclasses but also overridable at least for my understanding but the "non" should then be removed from the title.
I know this proposal is not directly related to this, but if we are ever going to have protected
members, we should probably want to deal with private
.
As you yourself say, similar things should look similar. Having the _
prefix meaning private
and an actual protected
keyword will probably look too asymmetrical, and also be confusing to people non-familiar with the language (IMO, the _
prefix is already confusing).
I would love to see that. Especially as it looks totally ugly to use _properties as constructor and there is even a lint discouraging it so you need initilizers. Am 22. Mai 2024, 20:30 +0200 schrieb Mateus Felipe C. C. Pinto @.***>:
I know this proposal is not directly related to this, but if we are ever going to have protected members, we should probably want to deal with private. As you yourself say, similar things should look similar. Having the prefix meaning private and an actual protected prefix will probably look too asymmetrical, and also be confusing to people non-familiar with the language (IMO, the prefix is already confusing). — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>
There are different scopes of privacy. "protected" is also kind-of-private, but it's private for the class and all the descendants.
The declaration int _foo
in a class is private to the library, not to the class. If private x
is introduced, then it must be private to the class (not visible even to descendants), so it doesn't have the same visibility as _x
. Both kinds have their uses.
Does dart have a concept of package-private sub-packages (libraries)? The fact that the libraries in /src
directory can be accessed from the outside via import 'package:name/src/foo.dart'
breaks their assumed "privacy". We can also envision package-private variables, functions, methods, classes etc. Do you want to support all of those? If not, why not?
(should /src
directory be treated as a private sub-package? Its ontological status is unclear.)
Regardless, I find _foo
syntax for library-private variables very convenient. These variables are so common that special-casing their syntax is well-justified IMO. Dart won't be the same language without this convention.
@tatumizer I agree, that it would make sense to have int _foo
as well as private int foo
where the later one would really mean class private.
For my understanding library private means accessible to everything inside one file/part-of files, but not from out side of that. There is no concept of a package as a library as such.
Fixed title to not admit first version was non-virtual.
Dart currently has no other privacy than library privacy. Library privacy can emulate most other kinds of first-party privacy, but not protected, because protected is explicitly about hanging rights to some, but not all, third party code.
By first-party privacy I mean access protection against code not written by the same author, but letting the author themselves ignore it. With library privacy, the author gets to decide the granularity.
Protecting against the same author, who can change the code, it's not as big a priority. Having class private declarations is more about avoiding accidental name clashes between implementation details, than actually protecting against access, file/library protection is sufficient for the latter.
Protecting against the same author, who can change the code, it's not as big a priority. Having class private declarations is more about avoiding accidental name clashes between implementation details, than actually protecting against access, file/library protection is sufficient for the latter.
I was talking more about syntactic similarities. I actually don't dislike the fact that we have library privacy. You can do anything we can do with what a private
keyword would be able to do (by using different files) and more (I think what people dislike about it is that you have to put everything in the same file).
I was talking specifically about syntax. Having one as a name prefix (_
) and the other as a keyword (protected
) feels somewhat asymmetrical to me, considering that both do similar things (protecting code access), although in different levels.
A prefix private
to declare a library private declaration could work for any statically resolved declaration. It would simply not be seen from any other library. It might cause more name collisions to use non-_
names, but you could still use those of you wanted to.
The reason to use a name prefix for library privacy is instance members, and dynamic invocation in particular.
If two classes in a superclass chain both introduce a private name, then the two are considered different names.
If we used private
on the declarations instead, then they should still be considered different names, but I've cannot see that at the invocation.
If a superclass in the same library has a private int foo;
field, and another superclass from another library adds a public void foo()
method, then either
this.foo
can only regret to one. (That's obviously fixed by renaming the library private variable, but again, you start wanting to prefix your library private variables, to avoid such churn.)For dynamic
invocation, it would need to know the library it's called from, to know which foo
it should find out skip. That's doable, but also error prone.
With the leading _
, one can see at the user-site that the library's private is wanted.
For protected
, which is cross library, using a modifier is easier, because it prohibits most of the problem cases.
It could also use %id
as name, and I'm sure it would work. There'd be no way to make a protected member public, would just have to forward to it instead.
But all the good name prefixes are taken.
This is an attempt at a full feature specification for protected instance members, as another solution to #835. Edit: And this is version 2 where the protected members can be overridden by subclasses.
Dart protected instance members
Dart has only one notion of declared access restriction: Library privacy.
Having private names solves two problems: Avoiding internal implementations having name clashes with third-party code, and ensuring what third-party code is able, and not able, to access, which makes it easier to ensure invariants.
Since the author controls the size and scope of a library, and is expected to be fully cooperative with all code they have the power to modify anyway, that’s a rather sweet spot of privacy. By scoping a library to a single class, or to what would be an entire directory of files in another language, the author can choose anything between class privacy and “package privacy”. It’s an accessibility where the author can control the scope.
One accessibility feature that other object oriented languages have, and that Dart cannot emulate with library privacy, is protected access: Instance members of a class which can only be accessed from subclasses which extend that class.
Protected members are useful for reusable libraries. A library can expose a base or skeleton class that third-party subclasses can extend to provide customized classes. However, if the subclass needs to interact with the base class, it can currently only do so if the base class exposes public API, which then contaminates the public API of the subclass with operations that end-users shouldn’t be using.
Dart provides the
@protected
annotation which makes the analyzer warn if a non-subclass invokes the annotated member. That’s not a safe solution, the API is still public and accessible, anyone can choose to ignore the warning.Instance protected members
We add instance members that can only be accessed and invoked by subclasses. Such a member can be declared in a
class
,enum
ormixin
declaration. It’s available to any class which has theclass
orenum
, or an application of themixin
, in its superclass chain. This includes the class itself — and for enums, nothing else.Syntax
The immediate choice is to make
protected
a built-in identifier and use it as a modifier on any instance member declaration:Since protected members cannot be static, we just have to decide where it goes relative to other modifiers. Let’s say after
external
, beforeabstract
, approximately the same place asstatic
.Let’s go with this, it can be changed if we have a better idea.
The grammar is updated to allow
protected
on any non-static, non-constructor member declaration. (Grammar taken fromDart.g
, skipping theaugment
declarations for now. They may or may not need to repeat theprotected
.):Semantics
We ensure that the modifier is only allowed where it makes sense. We introduce the following new rules and changes, with anything not mentioned assumed to work the same for protected members as for any other instance member.
class
,mixin
orenum
declaration. (Not allowed insideextension
orextension type
declarations.)class
,enum
ormixin
declaration declares a protected member, and any superinterface of the containing declaration has a non-protected member signature with the same name. If a member is already public, the protected declaration is misleading and probably an error.protected
.The behavior of a correctly declared protected member is:
A protected member declaration works just like any other instance member declaration, except that the member signature it introduces into the interface is protected, unless any superinterface has a non-protected member with the same name. Protected interface members are special wrt. accessibility and inheritance through
implements
, but otherwise work as normal. An interface member can only be protected if all super-interface members of the same name are protected, otherwise it falls back to being “public”.The combined super-interface of a
class
,mixin
,enum
orextension type
declaration is computed almost like today, except:implements
clause are omitted. (If the implementation is not inherited, nor is the protected interface member signature.)This computation affects the interface of the declaration itself, if it does not declare any members with the same name. It also affects the members available to a mixin through its
on
types. A mixin has a protected member if itson
type does.A protected member is considered inaccessible when accessed through a typed member invocation for any receiver other than
this
. DoingFoo o = ...; o.id();
whereFoo
's interface has a protected member namedid
, is a compile-time error, becauseFoo
does not have an accessible member namedid
.A protected member cannot be invoked through a dynamic invocation either. In a dynamic invocation, if an instance member is found which is protected in the interface of the receiver’s runtime type, dynamic invocation treats it as an inaccessible member and invokes
noSuchMethod
(it’s anoSuchMethod
-thrower).Protected members can only be invoked through
super
orthis
invocations (including the implicitthis.
for plain identifiers that are not in the lexical scope).If invoked through
super
, a protected member works just like any othersuper
member invocation, directly invoking the implementation of the member. Super member invocations do not use the interface of the superclass, but directly invokes the implementation. They ignore whether a member is protected.In a mixin, a
super
-member invocation can invoke a protected member of anon
type. As usual, anysuper
-member invocation in a mixin requires all concrete superclasses the mixin is applied to to have a valid implementation of that member.If invoked through
this
, a protected member works just like any other instance member invocation, except that it does not consider protected members as inaccessible.Summary
protected
, on any instance member of aclass
,mixin
orenum
, which marks it as protected in the class interface.this
orsuper
. That’s what the flag is used for, disallowing any other invocations.implements
, only through superclass relation.protected
declaration of the same name. Once public, always public.