Proposal to retain private[this] in Scala 3

Doesn’t even have to be a custom equals/compare. All case classes with private fields will also break immediately:

case class Plus(private val a: Int, private val b: Int) {
  val computed = a + b
}

There’s probably a couple of those in every library, so not much of the community build will survive if any!
I think the only hope for this change is to either make private forever mean private[C] in case classes (private[this] is already disallowed for case class fields), or have a transitionary period with private allowing cross-instance uses with a deprecation warning and a rewrite.

1 Like

My personal preference is that we should use private to mean “private to this instance” and private[MyClassName] to mean “private to this instance and its companion”.

I do not want to have the private/private[this] distinction inferred by the compiler. The whole point of private isn’t as some crutch for a not-smart-enough compiler; the point of private is as guardrails for overworked developers to ensure that they do not accidentally do the wrong thing.

“this member should be private to this instance” is very valuable information for any developer reading the code, and is a different and stricter guardrail than “this member should be private to this all instances of this class and their companion”. Both are valuable to have, but empirically “this member should be private to this instance” is what I want the vast majority of the time, so it should be given the shorter name

9 Likes

And how would a nested class, C.D, gain the visibility it now has into C? This affects more than companions and other instances of the same class.

There is a typical situation when neither ‘private’ nor ‘protected’ fits well.

abstract class A

open class B extend A 
// user class
class C extend B

There are no ways to declare methods in abstract class which are visible to open class and which are not visible to user class.

So the only way to make saftey abstraction with open class is to use aggregation.

abstract class A

open class B{
  private val a:A=???
}
// user class
class C extend B

It is not always comfortable.

Li writes,

My personal preference is that we should use private to mean “private to this instance” and private[MyClassName] to mean “private to this instance and its companion”.

This is a good suggestion. I prefer this now, too.

If I understood correctly, Li’s observation is that Scala 2’s private is equivalent to Scala 2’s private[MyClassName], where MyClassName is the class in which the variable is defined. So we are presented with a convenient opportunity, as follows:

Amended, specific proposal

  1. The syntax private[this] is dropped in Scala 3
  2. Scala 2’s private[this] becomes private in Scala 3
  3. Scala 2’s private[C] remains the same in Scala 3. i.e. the rules determining which C may be chosen are the same in Scala 2 as in Scala 3
  4. The effect of Scala 2’s private is now achievable with private[MyClassName] in Scala 3, where MyClassName is the class in which the variable is defined.

In this way Scala 3 is able to match all of Scala 2’s flavors of private access. But, Scala 3 gains the benefits of dropping the irregular private[this] syntax and private now meaning “private to this instance” (which seems to be what developers expect from the word “private”).

This amended proposal seems compatible with tooling to automatically upgrade Scala 2 code to Scala 3. The tool would simply rename private -> private[MyClassName] and private[this] -> private.

4 Likes

The previous thread: Change `private` to mean `private[this]`?

1 Like

Second this, the default private should honestly be private[this] and if you want other class instances to access the value than something like private[ClassName] makes perfect sense

2 Likes

Not to miss another feature of private[this] that I, particularly, rely on quite often: byte-code optimization for using field-level access rather than via a “get” method.

One could easily cure the syntactic anomaly by requiring private[this.type].

The cargo cult tells me that HotSpot always inlines accessor methods, but https://github.com/scala/scala/pull/8286 means that even private members will get to enjoy that particular optimization soon.

I don’t think private[this.type] is less weird than private[this]. Note that access qualifiers are not necessarily types. For example, when you write private[mypackage] or private[MyObject] , mypackage and MyObject are values, not types.

4 Likes

Sorry, my tone didn’t come through well over Discourse.

This was (obliquely) my point: there are plenty of places where term names are put in these (special) square brackets. The real question here is whether (current) private or (current) private[this] is more like what people want to mean when they say private, and I’m not sure if the current semantics are so incorrect as to warrant dropping private[this].

Also, I’d argue that having private mean the same as Java’s private is helpful to newcomers from Java.

1 Like

Just to clarify: the current state is that private[this] is inferred, and fields so inferred will not get accessor methods.

1 Like

One option there is to use @inline on vals to mean “use direct field access”—and would error out if the val can be overridden.

Right – but I think the point is that that’s bad from a maintenance POV. private[this] is, to some degree, future-protection – it’s a statement that this member isn’t supposed to leak outside of this particular instance.

But the inference mechanism here is, frankly, really obscure: I think most engineers will look at it, go “I have no idea why they’re putting this. here” (or just not notice it), and just ignore that, thus defeating the inference. If I’m reading the docs correctly (and perhaps I’m not), one engineer failing to say this. in one place will defeat the inference with no warnings.

I rarely use private[this] myself, so I don’t have much of a horse in this race. But I think there’s a valid point here, that the inferred version is obscure to the point of near-uselessness.

(A possible partial solution would be to issue a warning, or even an error, if there is a mix of this. and not within the class. That’s still too obscure, but at least provides some hope of being maintainable. If that’s already implemented, I’d suggest it be added to the docs.)

4 Likes

I think you’re misreading the doc (for reference, it’s available here).

The compiler now infers for private members the fact that they are only accessed via this .

My understanding is that "accessed via this" is not a syntactic restriction but a semantic one. In other words, the check would be made on type-checked code, where the tacit this accesses are elaborated.

Sorry, how does that break compared to the status quo?

1 Like

The abnormality more semantic than syntactic. Except for private[this], every other time you see private[X] it’s widening visibility, so private[this] restricting visibility instead is irregular.

1 Like

private[this] is disallowed for case class fields, because generated equals uses all case class fields.

1 Like

Since this is a case class, we expect it to have an auto-generated equals method that will, if the other object is also a Plus, compare field by field. To do so, it needs to read these fields for a different object of type Plus, which is allowed if they are private, but forbidden if they are private[this].

1 Like