← melang

Swift

melangの各noteで他言語と比較した、Swiftの設計をまとめたページ。ここに書かれている内容は、各noteの比較表から自動で集めたものです。

型

型推論が強く効く。

let age: Int = 20
let age: Int = 20
let name = "Alice" // 型推論で String
var nickname: String? = nil // Optional型でNullを表現

型推論が強力で型注釈を省略できる場面が多い。Kotlinと同様、Optional型によってNullの有無を型で表現する。

型注釈の位置

Kotlinと同様のpostfix構文で、引数・戻り値は明示が基本。

let age: Int = 20
let age: Int = 20
let name = "Alice" // 型推論でString

func greet(name: String) -> String { // 引数・戻り値は明示が基本
    "Hello, \(name)"
}

`name: Type`というpostfix構文をKotlinと共有する。関数の引数・戻り値は基本的に明示が必要で、ローカル変数の強い型推論とは対照的。クロージャの引数に限り、文脈から型を省略できる場合がある。

型変換

型名で変換し、失敗し得る変換は別のイニシャライザで表す。

let y = Int64(x)
let x: Int32 = 10
let y = Int64(x)

let big: Int64 = 3_000_000_000
let a = Int32(big)            // 範囲外だと実行時にクラッシュ
let b = Int32(exactly: big)   // 範囲外ならnil
let c = Int32(truncatingIfNeeded: big) // 切り捨てを明示

let n: Int? = Int("42") // 文字列 -> 数値は失敗するとnil

変換は`Int64(x)`のように型名のイニシャライザで書く。範囲外の値は実行時にクラッシュさせ、失敗を許容する場合は`exactly:`付きのイニシャライザでOptionalを返す。「クラッシュ / nil / 切り捨て」を引数ラベルで書き分ける点は参考になるが、失敗の理由を持たないOptionalで表す。

暗黙の型変換

数値型間の暗黙変換を持たず、明示的な変換を要求する。

let x: Int = 10
let x: Int = 10
let y: Double = x        // コンパイルエラー
let y: Double = Double(x) // OK

数値型間の暗黙変換を持たず、`Int`と`Double`を混在させる演算もコンパイルエラーになる。`Double(x)`のようにイニシャライザを介した明示的な変換が必要。

Enum / Sum Type

enumがassociated valueを持ち、payload付きvariantを表現できる。

enum Result { case success(User); case failure(String) }
enum Result {
    case success(User)
    case failure(String)
}

switch result {
case .success(let user):
    print("ok: \(user)")
case .failure(let message):
    print("error: \(message)")
} // 全caseを網羅しないとコンパイルエラー

`enum`自体がassociated valueを持てるため、variantごとに異なるデータを保持できる。`switch`は全caseの網羅をコンパイラソースコードを解析し、実行可能な形式(機械語や中間表現)に変換するプログラム。静的型付け言語では、実行前にコンパイラが型の不整合などをチェックする役割も担う。が要求し、melangが目指す設計に近い候補。

値の不変性

`let`と`var`を使い分ける。structは`let`にすると中身も固定される。

let x = ... / var x = ...
struct User { var name: String }
let a = User(name: "alice")
// a.name = "bob"        // コンパイルエラー: letのstructは中身も不変

var b = User(name: "alice")
b.name = "bob"           // OK

class Box { var value = 0 }
let c = Box()
c.value = 1              // OK: classは参照型で、letでも中身は変更できる

`struct`などの値型は代入時にコピーされるため、`let`にすれば中身も変更できず、共有による影響も出ない。一方で`class`は参照型で、`let`でもプロパティを変更できる。値型と参照型で不変の意味が変わる。

名前付き引数

引数ラベルが既定で呼び出し側に必須になり、省略するには`_`を明示する。

func foo(width: Int, height: Int)
func foo(width: Int, height: Int) {}
foo(width: 10, height: 20) // ラベルは既定で必須

func bar(_ width: Int, _ height: Int) {}
bar(10, 20) // _ を付けるとラベルを省略できる

他言語のように「名前付き引数を選べる」のではなく、引数ラベルを書くことが既定の呼び出し方になっている。省略したい場合は定義側で`_`を明示する必要があり、位置引数のみの呼び出しのほうが例外的な扱いになる。

関数の引数

引数は不変。書き換えるinoutは呼び出し側にも&を付けて示す。

func f(_ x: inout Int) / func f(_ x: borrowing T) / func f(_ x: consuming T)
func a(_ n: Int) {}
func b(_ n: inout Int) { n += 1 }
func c(_ t: borrowing Token) {}  // 借用(コピー不能な型向け)
func d(_ t: consuming Token) {}  // 消費(所有権を受け取る)

var n = 0
a(n)
b(&n) // 呼び出し側に & が付く

引数は不変で、書き換えを許す`inout`は呼び出し側にも`&`を付けて示す。呼び出しを読むだけで、書き換えられる可能性が分かる。melangは、書き換え自体をなくすことで同じ読みやすさを得る。Swift 5.9以降は`borrowing` / `consuming`で所有権値の持ち主を一つに定め、持ち主がいなくなったときに値を破棄する仕組み。Rustが代表例で、値を別の変数や関数へ渡すと持ち主が移り(move)、元の変数は使えなくなる。に近い渡し方も表せるが、こちらは呼び出し側に印が要らない。

if 文/式

通常の`if`はstatement。値を返す`if`式では`else`が必須で、早期脱出用に`guard ... else`がある。

if cond { a } else { b } / guard cond else { return }
// if式(Swift 5.9〜): elseが必須
let x = if cond { 1 } else { 2 }

// 手続きのif
if cond {
    process()
}

// guardはelseが必須で、必ずスコープを抜ける
guard cond else { return handle() }
process()

`if`はstatementだが、Swift 5.9から代入・return・クロージャの値として使える`if`式が加わった。式として使う場合は`else`が必須で、各branchは単一の式である必要がある。`guard`は`else`が必須で、`return`・`break`・`continue`・`throw`などでスコープを抜けなければならない。早期脱出という思想はmelangに近いが、`guard`という別の構文を足している。

I/O の明示

`async`と`throws`が関数の型の一部となり、呼び出し側にも伝播する。

func findUser(id: UserId) async throws -> User
func findUser(id: UserId) async throws -> User { /* ... */ }

func getUser(id: UserId) -> User {
    try await findUser(id: id) // NG: 自分もasync throwsが必要
}

func getUser(id: UserId) async throws -> User {
    try await findUser(id: id) // OK
}

`async`/`throws`はSwiftの関数型の一部として組み込まれており、呼び出し側は`await`/`try`を書く必要がある。ただし表しているのは非同期性とエラー送出であって、同期的なI/O(`FileManager`の一部API)はこれらを伴わずに呼べる。

言語名

一般名詞かつAppleの既存製品名と衝突していた。

「速い」を意味する一般名詞であり、発表当時すでに複数の同名ソフトウェア・商標が存在していた。Apple自身が開発・商標登録して統一を図ることで、後発でも実質的に名前を確保できた例。個人・小規模チームには真似しにくい、企業の商標力に依存した解決策。