Swift
melangの各noteで他言語と比較した、Swiftの設計をまとめたページ。ここに書かれている内容は、各noteの比較表から自動で集めたものです。
型
型推論が強く効く。
let age: Int = 20let age: Int = 20
let name = "Alice" // 型推論で String
var nickname: String? = nil // Optional型でNullを表現型推論が強力で型注釈を省略できる場面が多い。Kotlinと同様、Optional型によってNullの有無を型で表現する。
型注釈の位置
Kotlinと同様のpostfix構文で、引数・戻り値は明示が基本。
let age: Int = 20let 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 = 10let 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 -> Userfunc 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自身が開発・商標登録して統一を図ることで、後発でも実質的に名前を確保できた例。個人・小規模チームには真似しにくい、企業の商標力に依存した解決策。