I won't rehash the private-to-the-module debate here. I've always looked at it purely in terms of the public vs. private interface. From that perspective, I still don't find the argument that "it breaks encapsulation" a convincing one on the surface. However, this example from Max Samukha goes below the surface:
synchronized class C
{
private int x;
private int y = 1;
invariant() { assert(y == x + 1); }
}
void foo(C c, int x)
{
c.x = x;
c.y = x + 1;
}
That made me do a double take. Consider also, in place of an invariant
, a setter with a precondition like so:
class D
{
private int _x;
void x(int newX)
in (newX >= 100 && newX < 1000)
{
x = newX;
}
}
void main()
{
D d = new D;
d._x = 10;
}
In each case, directly accessing the variable elsewhere in the module bypasses all the checks.
So, the obvious solution, the same solution I have suggested numerous times in the private-to-the-module debate, is to move the class to a separate file. If all we are talking about is the public vs. private interface, and not the consequences of bypassing contracts and synchronization, then that's a suitable solution.
Unfortunately, this is a poor solution when synchronization and contracts are in the picture. It means these features are only fully effective when a class is in its own file.
Moreover, the precondition on D.x
can be bypassed within the class itself, so moving it to a separate module doesn't make that problem go away. Any member function can write to _x
without going through x
.
The other argument I make in the private-to-the-module debate, which again still holds if all we're talking about is private
, is that it doesn't matter---if you have access to the file, then you have access to the private members anyway.
And that doesn't hold here either. private
is intended to be accessible inside the module. Any constraints on access are arbitrarily set by convention (e.g., style guides). Class-level synchronization is intended to kick in on every function call. Invariants are documented to say that "relationships must hold for any interactions with the instance from its public interface", but then what's the point if it's so easily broken from elsewhere in the module?
And then there's function contracts, particularly on functions that write to member variables like in my example. This is a cheaper form of invariant in that it's only applied to this function and not to all functions in the class. But again, it's so easy to bypass.
From that perspective, I see massive gaping hole here. I've done a complete 180 on need for further restricting member variable access. But I'm seeing it more granularly than class-level and down to the function level.
After some spitballing, here's something I like:
class E
{
restricted int _y;
restricted(_y) void y(int newY) { _y = newY; }
}
A restricted member variable _y
can only be accessed in functions explicitly marked restricted(_y)
. No other member function can access that variable, and therefore neither can anything in the broader module.
The function y
is implicitly private, meaning it follows the rules of any private member (private-to-the-module). The _y
parameter to restricted
indicates not that the function itself is restricted, but that it is allowed to access _y
. So then this is also possible:
restricted(_y) protected foo { // okay to access _y }
restricted(_y) public bar { // ditto }
I imagine this would be useful:
restricted {
int _y;
void y(int newY} { _y = newY};
}
Any function declared in this restricted block has access to _y
. Multiple variables in a single block could be possible:
restricted {
int _x, _y;
void foo() { // this function is implicitly restricted(_x, _y) }
}
Restricted blocks are independent of each other. Functions in this block can't access member variables in other blocks.
One benefit of this would be that by tightly restricting write access to a single setter function like y()
, any in
contract that validates the new value is effectively a class invariant, but it's only run when this function is called instead of for every function in the class.
Should restricted member functions be allowed? E.g.:
class F {
restricted foo() { ... }
restricted(foo) bar { ... }
}
I don't know. If so, then functions in restricted blocks then be implicitly restricted
rather than private
.
============
Anyway, I thought I'd throw this out there for comment. Maybe someone will come up with a better idea, or take this one and run with it. Regardless, I can't be the one to write a DIP for it since I'm the DIP manager. So if anyone wants to, feel free.
Also, I haven't discussed this with anyone else yet, so I have no idea what Walter or Atila would say about it.
Destroy!