Kotlinのinline修飾子について


こんにちはsekitakaです。
AWSのバージニアリージョンがトラブってて開発しにくいのでQiita記事書いています。

[Anko-Layouts](https://github.com/Kotlin/anko/wiki/Anko-Layouts)を使ってみようと思ってコードを見てみたところ、`inline`という見慣れない修飾子を見つけたので調べてみました。

# inline修飾子とは
inline修飾子は主に関数を引数に持つ関数に使われる修飾子です。
この修飾子が付与された関数は、呼び出し元から関数が呼ばれるのでなく、関数の中身がコードとして展開されます。
C言語を知っている方はマクロをイメージすると分かりやすいと思います。

Javaにデコンパイルされたコードを見ると分かりやすいので確認してみましょう。

## inline関数

引数で渡されたラムダ式の関数を2回実行するinline修飾子が付与された関数`repeat`を,`main`から呼び出します。

```kotlin:Foo.kt
inline fun repeat(fn: () -> Unit) {
    fn()
    fn()
}

fun main(args: Array<String>) {
    repeat {
        print("foo")
    }
}
```

これをAndroid StudioのKotlin Bytecodeの機能でデコンパイルすると次のようになります。

```java:Foo.decompiled.java
@Metadata(
   mv = {1, 1, 6},
   bv = {1, 0, 1},
   k = 2,
   d1 = {"\u0000\u001a\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0002\b\u0003\n\u0002\u0018\u0002\n\u0000\u001a\u0019\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00040\u0003¢\u0006\u0002\u0010\u0005\u001a\u0017\u0010\u0006\u001a\u00020\u00012\f\u0010\u0007\u001a\b\u0012\u0004\u0012\u00020\u00010\bH\u0086\b¨\u0006\t"},
   d2 = {"main", "", "args", "", "", "([Ljava/lang/String;)V", "repeat", "fn", "Lkotlin/Function0;", "production sources for module app"}
)
public final class FooKt {
   public static final void repeat(@NotNull Function0 fn) {
      Intrinsics.checkParameterIsNotNull(fn, "fn");
      fn.invoke();
      fn.invoke();
   }

   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      String var1 = "foo"; // このあたり
      System.out.print(var1);
      var1 = "foo";
      System.out.print(var1);
   }
}
```

`main`から`repeat`が呼ばれるのでなく、`"foo"`を表示するコードが2回呼ばれている事がわかると思います。
このように引数のラムダ式を呼び出し元の関数内に展開して使用するのが`inline`修飾子の役割です。

## 非inline関数
inlineでないrepeat関数がどのようにデコンパイルされるのかも見てみましょう。
`inline`修飾子を付けない以下のコードをデコンパイルします。

```kotlin:Foo.kt
fun repeat(fn: () -> Unit) {
    fn()
    fn()
}

fun main(args: Array<String>) {
    repeat {
        print("foo")
    }
}
```

結果は次の通りです。
ごらんのように`main`からは`repeat`関数を呼んでいます。`repeat`に渡すラムダ式はクラス内に見当たりませんが`@Metadata`にそれらしき記述があるので、ごにょごにょうまいことやっているのでしょう。

```java:Foo.decompiled.java
@Metadata(
   mv = {1, 1, 6},
   bv = {1, 0, 1},
   k = 2,
   d1 = {"\u0000\u001a\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0002\b\u0003\n\u0002\u0018\u0002\n\u0000\u001a\u0019\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00040\u0003¢\u0006\u0002\u0010\u0005\u001a\u0014\u0010\u0006\u001a\u00020\u00012\f\u0010\u0007\u001a\b\u0012\u0004\u0012\u00020\u00010\b¨\u0006\t"},
   d2 = {"main", "", "args", "", "", "([Ljava/lang/String;)V", "repeat", "fn", "Lkotlin/Function0;", "production sources for module app"}
)
public final class FooKt {
   public static final void repeat(@NotNull Function0 fn) {
      Intrinsics.checkParameterIsNotNull(fn, "fn");
      fn.invoke();
      fn.invoke();
   }

   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      repeat((Function0)null.INSTANCE); // ここ
   }
}
```

# どうしてinline修飾子が必要なの？
関数を引数にとったり戻り値として返すことは様々なオーバーヘッドがあるようです。詳しくは[公式ドキュメント](https://kotlinlang.org/docs/reference/inline-functions.html)の初めの方に書いてあります。
`inline`修飾子を使うことでプログラムのサイズは大きくなるが、パフォーマンスの向上になるんだそうです。
特に大きなループの中で毎回ラムダ式を実行するような場合に効果を発揮しそうですね。

# 注意点
呼び出し元の関数内に展開されるのは`return`なども同じなので、inline関数に渡すラムダ式やinline関数内で書いた`return`は呼び出し元関数の`return`になってしまいます。

先程の`repeat`関数へ渡す引数のラムダ式内で`return`するとfooは一度しか表示されずmain関数が終了してします。

```kotlin:Foo.kt
inline fun repeat(fn: () -> Unit) {
    fn()
    fn()
}

fun main(args: Array<String>) {
    repeat {
        print("foo")
        return // mainのreturn になる
    }
}
```

`List`の`forEach`なんかも実はinline関数なので、次のようなコードを書くとループの1つめの要素で`main`が終了してしまいます。

```kotlin
fun main(args: Array<String>) {
    val foo = listOf<String>("foo", "bar")
    foo.forEach { item ->
        print(item)
        return
    }
}
```

JavaScriptの匿名関数とかになれてるとうっかりハマることもあるかもしれません。

# まとめ
いかがでしたでしょうか。`inline`関数の特性を使いこなしてハイパフォーマンスなアプリ開発をしたいものですね。