📱

Swift - Kotlin 言語比較 その1

2023/10/18に公開

Swift - Kotlin 言語比較 その1

はじめに

こんにちは、株式会社アイスタイルで@cosmeアプリのiOSエンジニアをしている上野初仁(うえのはつひと)と申します。

アイスタイルには外部委託として参画しており、今回はこのようなテックブログを書く機会をいただきました。参画前はDartによるアプリ開発や、サーバサイドKotlinを使ったシステムの構築などをしていましたが、SwiftおよびKotlinによるスマホアプリ開発は参画して初めての経験となります。

今回はそういった自分の経験も踏まえ、アプリ開発で良く用いられている2つの言語(Swift, Kotlin)について、それぞれの言語の言語比較をしてみます。

そもそも Swift, Kotlin とは

Swift

公式ページ :

https://www.swift.org/

SwiftとはApple社が開発した言語で、iOSやmacOSなどのアプリ開発のための言語です。元々使われていたObjective-Cに比べわかりやすい言語体系となっています。

その後オープンソース化され、macOS, Linux, WindowsのそれぞれのOS上で動作する言語セットのバイナリが提供されています。

「わかりやすい」と書いていますが言語としてのわかりやすさと、アプリ開発での使いやすさ・わかりやすさは別物で、アプリ開発自体はとっつきにくい印象が私にはあります。これはObjective-Cとの相互運用可能ゆえの難しさや、UIKitやSwiftUIといったUI周りの仕様の複雑さや落とし穴にあるように考えます。そのため、この記事ではUI周りの実装については記載をせず、あくまで言語仕様に絞って記載していきます。

Kotlin

公式ページ :

https://kotlinlang.org/

KotlinとはJetBrains社が開発した言語で、Java言語の仕様と思想を元に、さらに使いやすい言語体系となるように作られた言語です。Javaとの相互運用ができる作りになっており、Java開発経験がある開発者ならスムーズに移行でき、初学者にもわかりやすい言語体系となっています。

オープンソースの元で開発されており、色々なプラットフォームでの開発や実行が可能です。Java仮想マシンなどの話はオーバースペック気味なこともあり、ここでは割愛しますが、スマホアプリだけでなく、サーバサイドのシステムでも利用が広がっています。

JavaとくらべNull Safetyになっている点が私は一番の利点だと思っています。他にも独特な非同期処理への対応などあり、個人的な印象としてはこの2つの言語の中で一番に習得したい言語・習得をオススメしたい言語となっています。

実際の言語比較

インクリメント・デクリメント

Swiftには通常の言語でいうところの インクリメントデクリメント がありません。代わりに代入演算子を使って += 1-= 1 します。

// Swift

var n = 1

n += 1 // n = 2

n -= 1 // n = 1
// Kotlin

var n = 1

n++ // n = 2 ← Kotlinではインクリメントが可能

n-- // n = 1 ← Kotlinではデクリメントが可能

n += 1 // n = 2 ← もちろん加算代入も可能

n -= 1 // n = 1 ← もちろん減算代入も可能

var, let, val

変数や定数の宣言には varletval などを使います。

変数宣言にはどちらの言語もvarを、定数宣言にはSwiftはlet、Kotlinはvalを使います。

varはVariable、letは英語のLet、valはValueから名付けられています。プログラミング言語でLETキーワードが使われたのは古くBASICやLispに遡れます。今ではJavaScriptなどでも利用されています。英語で「xを1とする」と表すとLet x be 1.となり、ここから引用されたのですね。

今回は変数宣言定数宣言と便宜上書いています。実際にはSwiftでは変数定数と呼べますが、Kotlinの公式の記述に沿うとミュータブルな変数リードオンリーな変数となります。

Swift Kotlin
変数宣言 var (Variables) var (Mutable Variables)
定数宣言 let (Constants) val (Read-Only Variables)
// Swift

var someVariable = 1 // ← 変数に1を設定

someVariable = 2 // ← 変数を2に変更 (OK)

let someConstant = 1 // ← 定数に1を設定

someConstant = 2 // ← 定数を2に変更 (NG) コンパイルできない
// Kotlin

var someMutableVariable = 1 // ← ミュータブルな変数に1を設定

someMutableVariable = 2 // ← ミュータブルな変数を2に変更 (OK)

val someReadOnlyVariable = 1 // ← リードオンリーな変数に1を設定

someReadOnlyVariable = 2 // ← リードオンリーな変数を2に変更 (NG) コンパイルできない

ここまでであれば「変数」「定数」とどちらも呼べそうなのですが、それが正確ではない理由を以下にまとめます。

ミュータブルな変数リードオンリーな変数とは

Kotlin公式の書き方ミュータブルな変数 (Mutable Variables)リードオンリーな変数 (Read-Only Variables)、ちょっとわかりづらいですね。まずはミュータブルとは何でしょうか。

ミュータブルとは

ミュータブルの反対語としてイミュータブルがあります。以下表にまとめます。

ミュータブル イミュータブル
日本語訳 変更可能 変更不可能
性格 一旦設定した値を変更可能 一旦設定した値は変更不可能

では、Kotlinのvalはイミュータブルな変数を示すかというとそうではなく、厳密にはリードオンリー(読み込みのみ)の変数という扱いになります。なぜでしょうか。

ミュータブルなリストイミュータブルなリスト

まず先に説明のために2つのリストについて紹介します。それがミュータブルなリストイミュータブルなリストです。
以下の2つの変数mutableListimmutableListはそれぞれ、ミュータブルとイミュータブルなリスト(Kotlinでいうところの配列)です。
変更可能か不可能かは宣言の時点で異なります。また注意していただきたいのは、どちらもvalで宣言している点です。ミュータブル・イミュータブルとvalvarは関係ないことがわかります。

// Kotlin

val mutableList = mutableListOf("a", "b", "c")

val immutableList = listOf("a", "b", "c")

valなのに変更可能(ミュータブル)、varなのに変更不可能(イミュータブル)

では実際にいくつかの例を示します。
リストに要素を追加するadd()を利用します。

下記のようにミュータブルなリストはvalで宣言しても要素の追加が可能です。

// Kotlin

val mutableList = mutableListOf("a", "b", "c") // ← ミュータブルなリストとして宣言
mutableList.add("d") // ← valなのに要素の追加可能

イミュータブルなリストにはそもそもadd()が宣言されていないため要素の追加ができません。
変更についても関数が宣言されていません。

// Kotlin

val immutableList = listOf("a", "b", "c")
immutableList.add("d") // ← Error: イミュータブルなリストにはそもそもadd()が存在しないためコンパイルできない

一方イミュータブルなリストでもvarで宣言されていたら書き込み可能なためリスト自体の上書きは可能です。もしvalで宣言していたらリードオンリーの変数を変更したためコンパイルエラーとなってしまいます。

// Kotlin

var writableList = listOf("a", "b", "c") // ← 書き込み可能なイミュータッブルなリストとして宣言
writableList = listOf("d", "e", "f") // ← 別のリストで上書き可能

val nonWritableList = listOf("a", "b", "c") // ← 書き込み不可能なイミュータッブルなリストとして宣言
nonWritableList = listOf("d", "e", "f") // ← ERROR: 別のリストで上書き不可能、コンパイルエラー

一方Swiftでは

一方Swiftでは、配列(Array)に要素を追加する関数append()が用意されています。その動作をvarletでみてみましょう。

varで宣言したリストにはappend()が正しく動作し要素追加されます。

// Swift

var variableList = ["a", "b", "c"]
variableList.append("d") // ← (OK) variableList = ["a", "b", "c", "d"]

letで宣言したリストではappend()は利用できずコンパイルエラーとなってしまいます。

// Swift

let constantList = ["a", "b", "c"]
constantList.append("d") // ← (NG) 定数のリストは変更できないためコンパイルできない

これらからもわかるように、Swiftでは宣言時に「変更可能か、不可能か」を指定するのにvarletを利用します。
SwiftとKotlin、かなり似ていますがこういったところで大きな差が出てきます。

SwiftとKotlinのArrayの違い

Swiftでは配列はArrayですが、Kotlinでは先に示したListとは別にArrayがあります。
Swiftでは先に見た通り、varで宣言するかletで宣言するかで要素の追加変更が可能かが決まります。

一方、Kotlinは、mutableListが要素の追加が可能なのに対し、Arrayは要素固定の配列となり要素の追加削除ができません。
その代わり、要素の変更は可能です。

// Kotlin

val someArray = Array(3) { 0 } // ← (0, 0, 0)
someArray[0] = 1 // (1, 0, 0)

ArrayはmutableListと同様にvalで宣言していても要素の変更可能なこと(ミュータブルであること)がわかります。

関数

SwiftとKotlinで関数の定義の仕方は若干の違いとなります。

// Swift

func someFunction(param: Int = 1) -> Bool {
    return param == 1
}
// Kotlin

fun someFunction(param: Int = 1): Boolean {
    return param == 1
}
  1. Swiftは関数定義にfuncを、Kotlinはfunを使う
  2. 戻り値は関数名のカッコの後にSwiftでは-> 戻り値型、Kotlinでは: 戻り値型を使う

nil or null

SwiftではOptionalを表すのにnilを、KotlinではNullを表すのにnullを使います。
OptionalとはSwiftのNullable(NULL許容)属性のことで、一般的な言語のNullと同じと考えて構いません。
SwiftでもObjective-Cのライブラリを利用する際などにNULLやNSNullが出てきますが、ここでは「あるよ」程度に説明を留めておきます。

Null安全

Swift、KotlinともにNull安全性をもった言語です。ではどのようにNull安全を担保するかコードで示してみます。

以下(1)では、それぞれNull許容型の値をNull非許容型の値に変換しています。SwiftではNil結合演算子??によって左項がOptionalだった場合は、右項の値(ここではdefault string)が設定されます。
同様にKotlinではエルビス演算子?:の左項、右項が用いられます。エルビス演算子とは?:がエルビスプレスリーの顔に見えるから名付けられたそうです。ちょっとかわいいですね。

// Swift

// (1)
let someOptional: String? = nil
let someNonOptional = someOptional ?? "default string"
// Kotlin

// (1)
val someNullable: String? = null
val someNonNullable = someNullable ?: "default string"

(2)ではそれぞれOptionalかNullかの判定をif文でして条件分岐しています。
Swiftはif let文を使うことでOptionalの値を一時的に非Optionalの値にバインドして利用できます。
Kotlinはif != null文を使うと、そのif文の中では非Nullであることが担保されます。

// Swift

// (2)
let someOptional: String? = nil
if let someNonOptional = someOptional {
    print(someNonOptional)
} else {
    print("default string")
}
// Kotlin

// (2)
val someNullable: String? = null
if (someNullable != null) {
    println(someNullable)
} else {
    println("default string")
}

(3)は(1)、(2)とちょっと違った方法でOptional, Nullを回避しています。
Swiftはguard let else文を使い、Optionalの場合はelseの中が実行され、非Optionalの場合は非Optionalであることがelseの外で担保されます。ただしreturnが書かれていることからもわかるように、関数内で使うことになります。
Kotlinはlet節を使って非Nullを担保しています。このSwiftとKotlinのコードは必ずしも同じ動作をしませんが、説明のためにあえて作成したコードとなっていますのでご了承ください。

// Swift

// (3)
let someOptional: String? = nil
guard let someNonOptional = someOptional else {
    print("default string")
    return
}
print(someNonOptional)
// Kotlin

// (3)
val someNullable: String? = null
val someNonNullable = someNullable.let { nonNull -> nonNull } ?: "default string"
println(someNonNullable)

Kotlinのlet

Kotlinでletが出てきたので簡単に説明します。Kotlinではlet節を使うと、その節に与えた値がNullでない場合に{}の中に処理が入ります。

(1)はNullでない値をそのまま利用しているため、値はitにバインドされます。

// Kotlin

// (1)
val someNonNullable: String = "test"
someNonNullable.let {
    println(it) // ← someNonNullableがNullでない場合、Nullでない値は it にバインドされ println() される
}

(2)ではnonNullにバインドさせているため、itではなくnonNullを使ってprintln()しています。

// Kotlin

// (2)
val someNonNullable: String = "test"
someNonNullable.let { nonNull -> // ← このように書くと it ではなく指定した値(ここでは nonNull)にバインドされる
    println(nonNull) // ← Nullでない値が表示される
}

if文

Swiftではif文につきもののカッコが必要ありません。また&&の代わりに,で区切っても良いことになっています。

// Swift

if a == b, b == c {
    print("OK")
} else {
    print("NG")
}
// Kotlin

if (a == b && b == c) {
    println("OK")
} else {
    println("NG")
}

switch casewhen

Swiftには他言語と同様switch caseが、Kotlinにはwhenがあります。

// Swift

switch n {
case 1:
    fallthrough
case 2:
    print("1 or 2")
case 3:
    print("3")
default:
    print("other")
}
// Kotlin

when (n) {
    1, 2 -> println("1 or 2")
    3 -> println("3")
    else -> println("other")
}

他言語のswitch caseにはcaseごとにbreakが必要でしたが、Swiftでは必要ありません。
その代わり例のように1と2をどちらも動かすにはfallthroughを使います。
一方Kotlinではwhenを使い例のように使います。

チェックする値がEnum型だった場合、Swift・KotlinともEnumを網羅するようにしないとコンパイルエラーが出ます。
これにより不要なバグの混入などを防ぐことができます。

Kotlinのifwhen

Kotlinのifwhenは式のため、その結果を値に代入可能です。

// Kotlin

// (1)
val a = 1
val someValue1 = if (a == 1) "OK" else "NG" // ← someValue1 = "OK"

// (2)
val n = 3
val someValue2 = when (n) {
    1 -> "one"
    2 -> "two"
    else -> "other"
} // ← someValue2 = "other"

私はKotlinのこの機能、もっと他の言語に波及すればよいのにと考えています。と書いていたら、この原稿のレビューで「Swiftも書けるようになるよ」とツッコミが。Swift 5.9になるとifは式として認識されるそうです(やったね)。

おわりに

今回基本的な部分の言語仕様の差についてまとめました。今後続編でfiltermap、クラスや構造体など応用的な部分について説明できればと考えています。
今後、Compose Multiplatformの普及などでSwift話者がKotlinを触る機会も増えてくるのではと考えています。その一助になれば幸いです。

アイスタイルでは@cosmeアプリを一緒に開発してくれるメンバーを募集中です。一緒により一層すばらしいプロダクトにしていきませんか。ご応募をお待ちしています。

iOSエンジニア

https://open.talentio.com/r/1/c/isytyle_career/pages/43019

Androidエンジニア

https://open.talentio.com/r/1/c/isytyle_career/pages/43022

株式会社アイスタイル

Discussion