はじめに

2026年3月、Swift 6.3 がリリースされました。今回のアップデートでは、C 言語連携の正式 API 化やモジュール名の曖昧さ解消、Swift Testing の改善など、日々の開発で「地味に困っていた」部分が解消される機能が多数含まれています。

この記事では、特に実務でインパクトの大きい 4つの機能 を、Before / After 形式で紹介します。「既存コードをどう書き換えればいいか」がすぐわかる構成にしています。

  • @c 属性 — C 言語との相互運用が公式 API に
  • モジュール名セレクタ :: — 名前衝突の解消
  • Swift Testing の改善 — Warning Issues / テストキャンセル
  • ライブラリ向けパフォーマンス制御 — @specialized / @inline(always) / @export

公式リリースノート: Swift 6.3 Released | Swift.org

@c 属性 — C 言語との相互運用が公式 API に(SE-0495)

こんなとき困っていませんでしたか?

Swift で書いた関数を C や C++ のコードから呼び出したい場面は、組み込み開発やクロスプラットフォームライブラリの開発で頻繁にあります。これまでは @_cdecl というアンダースコア付きの 非公式属性 を使う必要がありました。

非公式 API は将来の Swift バージョンで予告なく変更・削除されるリスクがあり、プロダクションコードで使うには不安が残るものでした。

Before: @_cdecl(非公式 API)

// Swift 6.2 以前: アンダースコア付きの非公式 API
@_cdecl("mirrorCName")
func mirror(value: CInt) -> CInt {
    return value
}

After: @c(公式 API)

// Swift 6.3: 公式属性として安定化
@c(mirrorCName)
func mirror(value: CInt) -> CInt {
    return value
}

カスタム名が不要な場合は、さらにシンプルに書けます。

// 関数名がそのまま C の関数名になる
@c func foo() {}

// 生成される C ヘッダ:
// void foo(void);

enum も C 互換に

Swift の enum を C 側から利用可能な形で公開できます。

@c
enum CEnum: CInt {
    case first
    case second
}

既存の C ヘッダの実装を Swift で書く

@c @implementation を組み合わせることで、既存の C ヘッダで宣言された関数の実装を Swift で提供できます。これにより、C ライブラリを段階的に Swift へ移行することが可能です。

// C ヘッダ (mylib.h)
int cImplMirror(int value);
// Swift で実装を提供
@c @implementation
func cImplMirror(_ value: CInt) -> CInt {
    return value
}

移行ポイント

@_cdecl@c の移行は 機械的な置換 で対応できます。

Before

After

@_cdecl("name")

@c(name)

@_cdecl("name") + 関数名と同名

@c (名前引数を省略可)

参考: SE-0495: C-compatible functions and enums

モジュール名セレクタ :: — 名前衝突の解消(SE-0491)

よくあるケース: モジュール名と型名が被る

複数のモジュールをインポートしたとき、同じ名前の型や関数が衝突するケースがあります。従来は ModuleName.TypeName で解決しようとしましたが、ネストされた型と区別できない 問題がありました。

Before: Module.Type 記法の問題

// モジュール RocketEngine に以下が定義されている:
// public struct RocketEngine { ... }
// public struct Fuel { ... }

import RocketEngine

// Fuel を使いたいが...
let fuel = RocketEngine.Fuel()
// コンパイラは RocketEngine「構造体」の中のネスト型 Fuel を探してしまう

After: Module::Type 記法(モジュールセレクタ)

import RocketEngine

// :: でモジュールレベルの名前を明確に指定
let fuel = RocketEngine::Fuel()
// RocketEngine モジュールのトップレベル Fuel 型として正しく解決される

実践例: Task の名前衝突を回避する

Swift Concurrency の Task は広く使われる名前ですが、プロジェクト内に同名の型があると衝突します。

struct Task {
    let title: String
    let dueDate: Date
}

// Swift Concurrency の Task を使いたい場合
let asyncTask = Swift::Task {
    await fetchData()
}

同一モジュール内のシャドウイング解消

ネストされた型がトップレベルの型をシャドウイングしている場合にも使えます。

// モジュール NASA 内
struct Scrubber { ... }

struct LifeSupport {
    struct Scrubber { ... }  // トップレベルの Scrubber をシャドウイング
}

extension LifeSupport {
    // トップレベルの Scrubber を明示的に参照
    func makeMissionScrubber() -> NASA::Scrubber { ... }
}

拡張メンバーの曖昧さ解消

異なるモジュールが同じ型に同名のネスト型を追加している場合にも対応できます。

// IonThruster モジュール
extension Spacecraft {
    public struct Engine { ... }
}

// RocketEngine モジュール
extension Spacecraft {
    public struct Engine { ... }
}

// 使う側: どちらの Engine か明示できる
func makeIonThruster() -> Spacecraft.IonThruster::Engine { ... }
func makeRocketEngine() -> Spacecraft.RocketEngine::Engine { ... }

移行ポイント

  • 名前衝突が 実際に問題になっている箇所のみ 段階的に適用すれば問題ありません
  • 既存の Module.Type 記法はそのまま動作するので、破壊的変更なし
  • 新しいモジュールをインポートして衝突が発生したときに :: で解決する使い方が自然

参考: SE-0491: Module selectors for name disambiguation

Swift Testing の改善

Warning Issues — テストを失敗させずに警告を記録する(ST-0013)

「失敗ではないが気になる状態」を記録したい

テスト中に「失敗ではないが気になる状態」を検出したとき、適切な記録手段がありませんでした。Issue.record() を使うとテストが失敗扱いになり、print() で出力してもテスト結果に紐づきません。

Before: 中途半端な記録方法

@Test func validateImageSimilarity() {
    let similarity = compareImages(expected, actual)

    if similarity < 0.90 {
        // テスト失敗
        Issue.record("画像の一致率が低すぎます: \(similarity)")
    } else if similarity < 0.95 {
        // 失敗にはしたくないが記録したい...
        print("⚠️ 画像の一致率がやや低い: \(similarity)")  // テスト結果に残らない
    }
}

After: severity: .warning で警告レベルの記録

@Test func validateImageSimilarity() {
    let similarity = compareImages(expected, actual)

    if similarity < 0.90 {
        Issue.record("画像の一致率が低すぎます: \(similarity)")  // テスト失敗
    } else if similarity < 0.95 {
        Issue.record(
            "画像の一致率がやや低い: \(similarity)",
            severity: .warning  // テストは成功、警告として記録
        )
    }
}

ユースケース

シナリオ

severity

スナップショットテストで軽微なピクセル差分がある

.warning

セットアップ時にリトライが発生した

.warning

非推奨 API の使用を検出した

.warning

アサーションが失敗した

.error(デフォルト)

テスト中止 — 安全にテストをキャンセルする(ST-0016)

パラメタライズドテストで特定ケースをスキップしたい

パラメタライズドテストで、特定の引数の場合にテストを中止したい場面があります。従来は withUnsafeCurrentTask で Task をキャンセルするという unsafe な方法しかありませんでした。

Before: unsafe なタスクキャンセル

@Test(arguments: Species.all(in: .dinosauria))
func areAllDinosaursExtinct(_ species: Species) {
    if species.in(.aves) {
        // 鳥類は絶滅していないのでスキップしたいが...
        withUnsafeCurrentTask { $0?.cancel() }  // unsafe!
        return  // 明示的な return も必要
    }
    #expect(species.isExtinct)
}

After: Test.cancel() で型安全なキャンセル

@Test(arguments: Species.all(in: .dinosauria))
func areAllDinosaursExtinct(_ species: Species) throws {
    if species.in(.aves) {
        // 鳥類はスキップ(理由も記録される)
        try Test.cancel("\(species) は鳥類のためスキップ")
    }
    #expect(species.isExtinct)
}

改善ポイント

項目

Before

After

API

withUnsafeCurrentTask

Test.cancel()

安全性

unsafe(型安全でない)

型安全

制御フロー

return が必要

throw で自動的に中断

キャンセル理由

記録できない

文字列で記録可能

スコープ

タスク全体

パラメタライズドテストの当該ケースのみ

参考: ST-0016: Test cancellation / ST-0013: Test Issue Warnings

ライブラリ向けパフォーマンス制御(@specialized / @inline(always) / @export)

ライブラリのジェネリクスが遅い問題

Swift パッケージやライブラリを公開するとき、ジェネリクスのオーバーヘッドやクロスモジュールの最適化が効かない問題に直面することがあります。これまではアンダースコア付きの非公式属性で対処していました。

Before: 非公式属性

// ジェネリクスの特殊化(非公式)
@_specialize(where Self == [Int])
extension Sequence where Element: Numeric {
    public func sum() -> Double {
        reduce(0) { $0 + Double($1) }
    }
}

// インライン化の強制(非公式な記法)
@inline(__always)
func criticalPath() { ... }

After: 公式属性として安定化(SE-0460)

// ジェネリクスの特殊化(公式: @specialized + where 句で型を指定)
@specialized(where Self == [Int])
@specialized(where Self == [UInt32])
extension Sequence where Element: Numeric {
    public func sum() -> Double {
        reduce(0) { $0 + Double($1) }
    }
}

// インライン化の強制(公式な記法)
@inline(always)
func criticalPath() { ... }

// 実装の公開: クライアントが特殊化・インライン化できるようになる
// 従来の @_alwaysEmitIntoClient に相当する公式 API
@export(implementation)
public func expose() { ... }

@specializedwhere 句で特殊化する具体型を指定します。複数の型に対して特殊化したい場合は、属性を複数付与します。@export(implementation) は ABI 安定ライブラリの関数実装をクライアントに公開し、クライアント側コンパイラが最適化できるようにする属性です。

移行ポイント

Before(非公式)

After(公式)

対象

@_specialize(where T == ...)

@specialized(where T == ...)

ライブラリ開発者

@inline(__always)

@inline(always)

ライブラリ開発者

@_alwaysEmitIntoClient

@export(implementation)

ライブラリ開発者

補足: これらの属性は主にライブラリ開発者向けですが、アプリ開発者も依存ライブラリがこれらを採用することで 間接的にパフォーマンスの恩恵 を受けられます。

参考: SE-0460: Explicit Specialization

まとめ

Swift 6.3 で追加された主要機能を Before / After 形式で紹介しました。

機能

ユースケース

移行難易度

@c 属性

Swift 関数を C から呼び出す

低(機械的な置換)

モジュールセレクタ ::

名前衝突の解消

低(必要な箇所のみ適用)

Warning Issues

テスト警告の記録

低(新規 API の追加利用)

テストキャンセル

パラメタライズドテストのスキップ

低(unsafe コードの置換)

パフォーマンス制御

ライブラリの最適化

低(属性名の置換)

いずれの機能も 移行コストが低く、段階的に適用できる のが特徴です。特に @_cdecl@_specialize などの非公式 API を使っている場合は、早めに公式属性へ移行しておくことをおすすめします。

参考