`restricted` member variables

Mike Parker aldacron at gmail.com
Mon Jun 20 11:04:16 UTC 2022


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](https://forum.dlang.org/post/yhrioqcjmnipkrvlrpve@forum.dlang.org) goes below the surface:

```d
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:

```d
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:

```d
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:

```d
restricted(_y) protected foo { // okay to access _y }
restricted(_y) public bar { // ditto }
```

I imagine this would be useful:

```d
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:

```d
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.:

```d
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!





More information about the Digitalmars-d mailing list