Difference between revisions of "Access specifiers and visibility"

From D Wiki
Jump to: navigation, search
(Why D should not mimic private semantics)
(Current state of affairs in C++)
Line 105: Line 105:
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 
<pre>
 
<pre>
 
test.cpp:9:15: error: 'a' is a private member of 'Test'
 
test.cpp:9:15: error: 'a' is a private member of 'Test'
Line 113: Line 114:
 
             ^
 
             ^
 
</pre>
 
</pre>
 +
 
==== protected ====
 
==== protected ====
 
Similar to private, but allows access for descendants.
 
Similar to private, but allows access for descendants.

Revision as of 19:14, 19 May 2015

Description

Current state of affairs in D

Access specifier (protection attribute) meaning

Mostly copied from http://dlang.org/attribute.html#ProtectionAttribute

private

Private means that only members of the enclosing class can access the member, or members and functions in the same module as the enclosing class. Private members cannot be overridden.

dlang.org states that "Private module members are equivalent to static declarations in C programs." but this is wrong, they have external linkage:

module sample;

private void func1(int) { }
void func2(int) { }
$ dmd -c sample.d
$ nm -a sample.o | grep func
00000000 t .text._D6sample5func1FiZv
00000000 t .text._D6sample5func2FiZv
00000000 T _D6sample5func1FiZv
00000000 T _D6sample5func2FiZv

package

Package extends private so that package members can be accessed from code in other modules that are in the same package. This applies to the innermost package only, if a module is in nested packages.

protected

Protected means that only members of the enclosing class or any classes derived from that class, or members and functions in the same module as the enclosing class, can access the member. If accessing a protected instance member through a derived class member function, that member can only be accessed for the object instance which can be implicitly cast to the same type as ‘this’. Protected module members are illegal.

public

Public means that any code within the executable can access the member.

export

Export means that any code outside the executable can access the member. Export is analogous to exporting definitions from a DLL.

Global static

Global static storage class currently is a no-op storage class in D, global symbols with one are compiled but ignored.

Name lookup

http://dlang.org/attribute.html#ProtectionAttribute : Protection does not participate in name lookup. In particular, if two symbols with the same name are in scope, and that name is used unqualified then the lookup will be ambiguous, even if one of the symbols is inaccessible due to protection. For example:

module A;
private class Foo {}
module B;
public class Foo {}
import A;
import B;

Foo f1; // error, could be either A.Foo or B.Foo
B.Foo f2; // ok

One other obvious consequence of this - in case private symbol is only possible lookup, error message will show to it, instead of issuing unknown symbol.

What is missing

  • There is currently no way in D to mark symbols for internal linkage, saying "this an implementation detail, you should not even know this one exists". This is an important module-level encapsulation tool which also somewhat guarantees that those symbols can't be linked to by accident by some other module and you are free to change them keeping binary interface same.
  • Name clash between public and private symbols has also been stated as unneeded and useless feature that makes possible to break a compilation of a project by changing private name. It is also impossible to use an UFCS function now if class already has private one with same signature.

Complications

  • Compile-time reflection, i.e. serialization libraries or @attribute scanners. Limiting access for __traits may forbid certain currently working idioms.
  • Symbol leakage via aliases or function arguments.
private struct _Hidden { }
alias const(_Hidden) UseMe;
void func(_Hidden input) { }

Currently it is all valid as _Hidden symbol is perfectly accessible, just prohibited from direct usage. If some true internal linkage storage class / specifier is introduced (private or not) this case needs to be defined in smallest details. See Access_specifiers_and_visibility#Module-scope_analogies for how it is handled in C++.

  • Templates do symbol look up in their original scope, not in the scope that they're instantiated. They only do symbol lookup in the scope that they're instantiated in if they're mixed in. Consequences - unknown, but it is related to name lookup.

Related bugzilla issues

Current state of affairs in C++

Access specifier (protection attribute) meaning

private

private as an access specifier is defined only for classes/structs. It does not hide symbol, but prevents usage:

class Test
{
    private:
        int a;
};

int main()
{
    Test t; t.a = 42;
    return 0;
}
test.cpp:9:15: error: 'a' is a private member of 'Test'
    Test t; t.a = 42;
              ^
test.cpp:4:13: note: declared private here
        int a;
            ^

protected

Similar to private, but allows access for descendants.

public

Default one for globals (you can't have any other access specifier for globals), "if you can see symbol - you can access it". Also default for struct members.

Module-scope analogies

C++ does not have a modules in D sense. It is based on translation units (*.cpp) and headers are just copy-pasted upon include, so module-level access specifiers make no sense to C++. However, there is an "unnamed namespace" feature, that forces internal linkage for a symbol:

// sample.cpp
void func1(int) { }

namespace
{
    void func2(int) { }
}
$ clang++ -c sample.cpp
$  nm -a ./sample.o | grep func
00000000 T _Z5func1i

One of interesting cases - what happens when hidden symbol becomes the part of public interface? Consider this sample:

// sample.cpp
namespace
{
    class Hidden
    {
    };
}

class Test
{
    public:
        void func(Hidden) { }
};

// if func() referencing Test will be skipped, then
// no traces of Test::func will be found in object file.
// Can't find part of standard that explains it.
void func(Test t)
{
    t.func(Hidden());
}
$ clang++ -c sample.cpp
$ nm -a sample.o | grep Hidden
00000020 t _ZN4Test4funcEN12_GLOBAL__N_16HiddenE

While symbol somewhat leaked into external linking, it has special mangling and, I suppose, impossible to use without some assembler-like forging. Note that it is impossible to share Test class definition via header as Hidden symbol won't resolve within any other translation unit.

Why D should not mimic private semantics

Jonathan M Davis, on name clash issue:

C++ doesn't have module-level access modifiers or UFCS, so it's completely unaffected by this. In C++, you can't add functions to classes from the outside, so there's no concern about adding private functions to a class breaking code using that class. And you can't add a private function to a module, because there are neither modules nor private, free functions, so you aren't going to get breakage from adding a private function, because you can't add a private function.

He again:

Regardless can't override private functions even within a module, because in D, private and package functions are never virtual and therefore cannot be overridden. If you want to be able to override them, you have to make them public or protected.

In C++, you can make private functions virtual and override them, but that's not possible in D, primarily because we took the simpler route of tying the virtuality of a function to its access level rather than having to explicitly mark functions as virtual.

Requirements for DIP

Everyone here has raised some good points. But this isn't a simple issue, so I suggest getting together and preparing a DIP. A DIP should address:

1. what access means at module scope 2. at class scope 3. at template mixin scope 4. backwards compatibility 5. overloading at each scope level and the interactions with access 6. I'd also throw in getting rid of the "protected" access attribute completely, as I've seen debate over that being a useless idea 7. there's also some debate about what "package" should mean

I.e. it should be a fairly comprehensive design addressing access, not just one aspect of it.

(c) Walter