Ideal Language

Defects allowed by imprecise access modifiers

I recently fixed a defect that I felt the compiler could have caught. Some initialization code was not being run as I was calling a protected function instead of the correct public one. With some simple additional access modifiers it would be easy to prevent this type of error.

The problem

My code had a fairly common structure: a public function which wraps an internal protected one. In my case it was part of Fuse’s layout code in the Element class:

1
2
3
4
5
6
public float2 ArrangeMarginBox(float2 position, float2 availableSize, SizeFlags availSet) {
    // ...common setup code...
    var sz = OnArrangeMarginBox(position, availableSize, availSet);
    // ...final layout code...
    return sz;
}

Where:

1
protected abstract float2 OnArrangeMarginBox(float2 position, float2 availableSize, SizeFlags availSet);

The problem lies in the protected modifier on the OnArrangeMarginBox. This is needed so the base class can call the derived implementations, but it also means all those implementations can call this function on other instances.

The problem surfaces for me since I have a container class including another element. During measuring it needs to measure the other element and I did this by accident:

1
2
3
protected override float2 OnArrangeMarginBox(float2 position, float2 availableSize, SizeFlags availSet) {
    return MyContent.OnArrangeMarginBox(poistion, availableSize, availSet);
}

I called OnArrangeMarginBox instead of ArrangeMarginBox, thus getting invalid results!

The simple solution

When I marked the OnArrangeMarginBox function as protected I really wanted to say that only the current instance can call that function. Another instance of the same type shouldn’t be able to call it. It’s actually a very common desire for protected functions and properties; they are meant to be available only to the current instance.

1
protected(instance) abstract float2 OnArrangeMarginBox(float2 position, float2 availableSize, SizeFlags availSet);

This would have caught my error quickly, since MyContent.OnArrangeMarginBox is no longer allowed; MyContent is not this instance thus I can’t call instance protected methods on it.

I’ve looked at a lot of my code in several projects and I’m starting to wonder if maybe instance protected isn’t the more reasonable default. Other than a few comparators it’s rare that I actually want my protected fields accessible by other instances. I’m sure I’ve seen at least one language where this is the case, but I can’t remember which.

The complex solution

I’m tempted to go one step further here. OnArrangeMarginBox should actually not be called by anything other than the ArrangeMarginBox function. It’s not really a standalone function: it’s a component of the public function.

I’d like to say:

1
callable_by(ArrangeMarginBox) abstract float2 OnArrangeMarginBox(float2 position, float2 availableSize, SizeFlags availSet);

Where callable_by(ArrangeMarginBox) specifies that only a single function may call this one.

This modifier would have prevented several defects I’ve had over the years. It’d block another situation I had with layout code as well. ArrangeMarginBox has a counterpart ArrangePaddingBox, along with it’s OnArrangePaddingBox partner. There are a lot of types that implement layout, and it’s an easy mistake to call OnArrangePaddingBox when we actually meant OnArrangeMarginBox. Markers like callable_by can help prevent this type of error.

Strictness over convention

A lot of projects use conventions to indicate extended modifiers, such as using a trailing underscore, different casing, or other naming patterns. I would much prefer that my compiler can validiate my code rather than me manually tracking such conventions. The compiler is just better at these fine details.

There’s always a balance that has to be reached with adding more modifiers. We want to write safe code, but we don’t want to lose the ease of programming new code. I think OOP is missing a lot of flow control modifiers though, making it confusing to write type hierarchies. I’d welcome the addition of modifiers that help me better manage my virtual functions.

9 replies »

  1. If I am not mistaken, Scala solved the problem — you add “this” to modifier. However it would be better to have instance access by default and “open” it on demand.

    • Yes, looking at my code I think a default of `this` would make sense. In most cases it is actually what I want. Only in certain comparison or construction code do I need actual cross-instance access.

  2. “I recently fixed a defect that I felt the compiler could have caught.” Which compiler? For what language? Your Leaf compiler? Or some other compiler?

    • This particular example comes from Fuse, which uses a C# derived language (custom compiler). I didn’t want to draw attention to the language itself here since I know it’s a common problem. It’s something I’ll definitely want to consider resolving for Leaf.

  3. Protected is like private. It will not allow access to the variable or function outside of itself and children. It is strange the compiler didn’t catch this but from the way you started this post, you were describing needing the method to be accessed from outside the class and children also. So protected should not have been considered

    • The distinction I’m showing is between two types of protected access. One allows the type, and its derived types, to access the field. The other allows only strictly the current instance from accessing the fields.

      For example, given this hierarhchy:
      BaseType <- DerivedType

      DerivedType is allowed to access the protected fields of BaseType. This is the common purpose of protected.

      However, consider this:
      var a = new DerivedType();
      var b = new DerivedType();
      Here, b can access the protected fields of instance a. This isn't what I normally intend with protected. I want to limit access to a single instance: a can access only a's protected fields, not b's.

  4. Ah, this is java script. That’s why the compiler didn’t catch the error. There is none :p I’m not having a go at you, I love posts like this, but just a little bit more information about the problem and you might not get so many strange comments :p Anyway, keep the posts coming :D

    • The example is actually from a C# derived language. I think protected works the same in most languages though (C++ for sure).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s