Should Superman run in underwear to be faster?


#1

As i see Kotlin doesn’t allow public inline function to access non-public members. In snippet below IDE highlights ‘underwear’ call with tip “public-api inline function cannot access non-public api …”. So the Superman is forced to exhibit his pants if he wants to use inline booster.
Yes, from the stand point of Java it’s correct since inline block is copied to the point of invocation where it should be able to access all it references. But in fact that creates hell for library developers and users because of mess of accessible members and compromising those that should not be accessible.

Is there a solution on the Kotlin roadmap?

class Superman {
    inline fun prepare(putOn: (String) -> String): List<String> {
        return (undewear() + "Red cape with 'S'").map { putOn(it) }
    }
    /*private*/ fun undewear(): List<String> {
        return listOf("Something", "that Superman would prefer", "not to show")
    }
}

Seems one of solutions is to let Kotlin compiler to generate public delegate member for each non-public one.


EDIT: For those who are interested here is related feature request [KT-11016]
(https://youtrack.jetbrains.com/issue/KT-11016) (Thanks @ilya.gorbunov)


#2

The issue here is not the “delegate” members (this technique is widely used in the compiler already), but language design concerns:

  • we want binary compatibility to be manageable

If a public inline function exposes anything, this anything, albeit declared private and exposed through synthetic accessors, becomes part of the a API, and changing it breaks client code.

It would probably make sense to introduce an explicit opt-in for such things, e.g.

supermans-underware private fun underwear() { ... }

It may come in the form of modifier or annotation on the private function that’s effectively non-private


#3

I’m not a chef in low-level language kitchen so i’d like to clarify it for myself. Please tell me where i’m wrong. (I’m not arguing, Just curious)

If we have that source code:

class Robin {
  val superman = Superman()
  fun letsRescueTheWorld() {
    superman.prepare { thing -> "<Super $thing />" }
  }
}

class Superman {
  inline fun prepare(putOn: (String) -> String): List<String> {
    return (underwear() + "Red cape with 'S'").map { putOn(it) }
  }
  private fun underwear(): List<String> {
    return listOf("Something", "that Superman would prefer", "not to show")
  }
}

Could the generated code be something like that ?:

class Robin {
  val superman = Superman()
  fun letsRescueTheWorld() {
    // inline transformation
    (superman.synthetic_underwear() + "Red cape with 'S'").map { thing -> "<Super $thing />" }
  }
}

class Superman {
  inline fun prepare(putOn: (String) -> String): List<String> {
    return (underwear() + "Red cape with 'S'").map { putOn(it) }
  }
  fun synthetic_underwear() = underwear()
  private fun underwear(): List<String> {
    return listOf("Something", "that Superman would prefer", "not to show")
  }
}

Than what is that “anything exposed by inline function that can break client code” ?

And if this assumption is in fact silly. let’s back to solutions suggested by you.
In particular by annotation do you mean something like Android @hide ?
And you did not mention are there any plans to implement any solution for that issue?


#4

I think Andrei meant that if it’s public in the class file, then users can use it, one way or another. The simplest path to misuse would be to call that function from Java. Although if the name is mangled, that particular pitfall becomes much less likely.

Otherwise the argument doesn’t seem to hold much water to me: it’s already possible to alter the privacy of methods using reflection (or agents, etc…).

I, for one, agree wholeheartedly that public inline functions should be able to call private functions.


#5

When you expose private fun underwear by referencing it in an inline function, it becomes effectively public. It’s not the exposing breaks client code, but changing those that have been exposed before.
For example could you tell, is it safe to remove private function and not break binary compatibility? The answer becomes not so straightforward when this function can be exposed through inlining.


#6

As i understand “binary compatibility” is a contract that i as a developer of library should respect to guarantee that code dependent from library will continue to link to it’s new version without errors. In other words i should retain public interface to the library and free to change internal implementation. Which includes removing private members. And thus i can mistakenly remove underwear function considering it is a part of implementation but in fact it is a part of public interface due to exposing in inline function.
And since “binary compatibility” is a set of human rules the developer should (voluntarily) follow why not just add one more rule for example in the form of annotation (as @abreslav suggested) that signifies that annotated private member is a part of public library interface and can not be removed without concerns of binary compatibility?
Hm… seems i’ve just said what @abreslav said in his reply :slight_smile:


#7

We have a draft design of how it could be implemented in our tracker, see KT-11016.

Currently it’s just a proposal, it has not been prioritized yet.


#8

That link says: :( You have no permissions to view this page
Searching for KT-11016 on Youtrack says: No issues found
Is there a way to view it?
Or is it better to open new feature request issue?


Thanks @abreslav it works.


#9

Works for me (in an anonymous browser window, too). Please check again: https://youtrack.jetbrains.com/issue/KT-11016


#10

Some form of name mangling (for code that should not be accessible to sourcecode). In particular, the set of valid symbol characters in the jvm is larger than the set of valid symbol characters in the Java language. The only restriction would be that the affected symbols would not be accessible from Java in the same module (only for internal visibility). Obviously languages that are more permissive in their symbols could use the symbol, but then again, if you use a symbol called private$foo then you are doing the Java equivalent of the C/C++ #define private public.

Perhaps marking things synthetic (like Java’s bridge methods) would work as well.


#11

Availability in the code is not really an issue, we use techniques that can handle it all over the place in Kotlin (there’s name mangling, and there’s synthetic accessors).

The only issue is that normally programmers can change/remove private declarations without affecting public APIs, and if such declarations are in any way exposed (e.g. through inlining), there will be unexpected breaking of APIs. This is why I said all we need is a modifier/annotation to mark exposed private functions explicitly: the API owner will see them, and take measures.


#12

You are right, I forgot about breaking abi compatibility. At the same time it would still be worthwhile to hide or “deprecate” the involved functions/types to encourage more managed use in the inline function.


#13

We’re going to allow referencing classes/members with internal visibility from public inline functions, provided that the internal members are annotated as @PublishedApi.