Method in companion object, or extension function?

Is there any practical difference between defining a function inside a companion object, compared to creating it as an extension function on the same companion object?

For example, here:

data class Foo(val s: String) { 
    companion object {
        fun from_as_companion(s: String): Foo {
            return Foo(s)
        }
    }
}

fun Foo.Companion.from_as_extension(s: String): Foo {
	return Foo(s)
}

fun main() {
    val f1 = Foo.from_as_companion("hello,")
    val f2 = Foo.from_as_extension("world")
    
    println("${f1.s} ${f2.s}")
}

From a code-style perspective I think the from_as_extension() approach is slightly more readable, because the reader has fewer levels of nesting to consider; the function body is indented just one level, compared to the 3 levels that function_as_companion is nested to.

There is a byte code difference that I don’t understand – if I look at the generated bytecode in Android Studio the code for both functions is the same except for the entry. from_as_companion looks like:

[...]
  public final from_as_companion(Ljava/lang/String;)Lapp/pachli/network/model/Foo;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
    // annotable parameter count: 1 (visible)
    // annotable parameter count: 1 (invisible)
    @Lorg/jetbrains/annotations/NotNull;()
   L0
    ALOAD 1
    LDC "s"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
[...]

while from_as_extension has an extra checkNotNullParameter:

  public final static from_as_extension(Lapp/pachli/network/model/Foo$Companion;Ljava/lang/String;)Lapp/pachli/network/model/Foo;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
    // annotable parameter count: 2 (visible)
    // annotable parameter count: 2 (invisible)
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 1
   L0
    ALOAD 0
    LDC "$this$from_as_extension"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
    ALOAD 1
    LDC "s"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V

Assuming this isn’t in any performance sensitive code are there any other reasons to avoid extension functions in this case?

I think there’s a subtle difference in scoping: while all code that has access to the companion object will automatically see all its (public) methods, code might not see the extension function if it’s not in scope (i.e. is not defined in this class/package, and hasn’t been imported).

(The other major difference with extension functions is the behaviour around subclasses — but of course you can’t subclass an object, so that’s not relevant here.)

I imagine there’d be a difference for reflection… dunno if that’s relevant to you, though. If you use reflection to look at Foo’s companion object and list all methods, you wouldn’t get the extension method, I don’t think.

Thanks both. I hadn’t thought about the scoping issue.