💨

【Linux】Swift 6で文字コード変換ができなくなった

2024/09/27に公開

Swiftにおける文字コード変換

SwiftのStringは内部的に文字列をUnicodeで扱っており、必要に応じて他のエンコーディングに変換することができます。
以下の例では、文字列をShiftJISエンコードのDataに変換したり、逆にDataからStringを生成します。

別の文字コードに変換
let data = "こんにちは".data(using: .shiftJIS)
指定文字コードとして読み込み
let string = String(data: data, encoding: .shiftJIS)

この操作はFoundationライブラリに実装されたStringへの拡張です。
FoundationライブラリはSwiftツールチェーンに同梱されており、実質的な標準ライブラリとして広く利用されています。

Swift 6とFoundation

Swift 6が2024年9月17日にリリースされました。5が出たのが2019年なので、5年ぶりのメジャーバージョンアップです。

Swift 6には多数の目玉機能がありますが、そのうちの1つにFoundationの実装移行というものがあります。
これまでFoundationの実装は2つに分かれていました。

A. Appleが内部ソースで管理するAppleプラットフォーム向けのFoundation。OSに同梱して配布している
B. Linuxなど、Apple以外のプラットフォーム向けのOSS実装swift-corelibs-foundation。内部的にはCoreFoundationに処理を委譲するものが多い

実装が2つに分かれていることで、これまで様々な問題がありました。

  • AとBで微妙にインターフェースが異なる(AでだけSendableがついてるなど)
  • Aだとスレッドセーフだけど、Bではスレッドセーフじゃない
  • Aにしかない実装がある

またAとB両方の課題として、CoreFoundationに強く依存しているというものがありました。
CoreFoundationはC言語で記述されたFoundationの内部実装です。これはObjective-Cとの親和性が高い実装となっており、Swiftからはラップして使う運用となっていました。事実、Bの実装には多くのCoreFoundationに対するグルーコードがあります。
CoreFoundationではSwiftの高度で安全な機能を利用できず、またラップして使うオーバーヘッドも好ましいものではありませんでした。

そこで、Swiftで新たに実装し直されたのがswift-foundationです。
各機能をSwiftで再実装してCとのオーバーヘッドをなくしつつ、同じ実装を全てのプラットフォームで利用して機能差異をなくすことができました。
JSONDecoderなどのクラスはパフォーマンスが2倍以上に向上したようです。

swift-foundationにある2つのターゲット

さてこのswift-foundationですが、これまでは1つだったモジュールが複数のモジュールに分かれました。現在は大別して2つのターゲットが定義されています。

  • FoundationEssentials
  • FoundationInternationalization

複数のターゲットに分かれることによって、未使用の機能をバイナリから削減しやすくなり、コンパイル時間や成果物のサイズに大きな恩恵をもたらします。

基本的にはほとんどの型がFoundationEssentialsに定義されており、Calendarや日付フォーマット、数値フォーマットなどのLocaleによって変わる処理がFoundationInternationalizationによる拡張で実装されています。
Localeごとの実装は大きくなりやすいので、分割の恩恵もその分大きいです。

swift-foundationの文字コード変換

前置きが長くなりましたがここで本題となります。

冒頭で紹介したdata(using:)メソッドやString.init(data:encoding:)についてですが、これらはFoundationEssentialsに実装されました。
そして、FoundationEssentials内ではUnicodeに関する処理のみ実装され、その他の文字コードに関してはCoreFoundationにフォールバックする実装になりました。

https://github.com/swiftlang/swift-foundation/blob/89129779c679dc3e173fa9b7c66e69bb6f6810de/Sources/FoundationEssentials/String/StringProtocol%2BEssentials.swift#L234-L241

これによって、フォールバック先のないAppleプラットフォーム以外の環境において、ShiftJISのような国固有の文字コードの変換が利用できなくなりました。


(Swift5.10でできた変換がSwift6.0でできなくなっている様子)

なぜFoundationInternationalizationではなくFoundationEssentialsにこの実装があるのでしょうか。これらのメソッドは大抵Unicode指定で用いられる上、Unicode実装は標準ライブラリに含まれていて追加実装がほとんどないため、多くのユースケースでコンパクトに利用できるようFoundationEssentials側に実装されたのではないかと思います。

残りの文字コードがFoundationInternationalization側でサポートされていない理由はわかりません。
FoundationInternationalizationにはICUの実装が含まれており、文字コード変換のために追加実装の負荷が大きいわけでもなさそうです。
既存のインターフェースとの互換性を考慮して諦めたのか、その他の理由があるのかはわかりません。

https://github.com/swiftlang/swift-foundation/issues/925

Linuxで日本語文字コードを扱う人がこの先生きのこるには

Issueは上がっていますが現状ないことはどうしようもなく、自前で文字コード変換ライブラリを導入するしかなさそうです。
文字コード変換のツールとして、有名なものだとnkfやiconvがあります。
これらを自前でビルドしてSwiftから利用できるようにすることが正攻法だと言えるでしょう。

私は文字コード変換に関する問題によく遭遇するため、それぞれについてSwiftパッケージを用意しました。

https://github.com/sidepelican/swift-nkf

https://github.com/sidepelican/xciconv

swift-nkfは単一のパッケージにまとまっていて利用が簡単です。nkfの実装がスレッドセーフではないためglobalActorで隔離して並行安全性を担保していますが、同時に1つの変換しか行えないためホットパスでの利用には不向きです。
xciconvはAppleプラットフォーム向けにはxcframeworkとして提供され、Linuxでは単にlibiconvを自前でインストールしてpkg-configから利用する構造になっています。Linuxでの利用は少々手間です。

おわりに

今回はデグレったケースの紹介となりましたが、サーバサイドにおけるSwiftエコシステムの進歩は凄まじく、サーバ用途でもSwiftは非常に便利な存在となってきています。
感覚的には、Rustの型による堅牢さや並行安全性を維持しつつ、メモリ管理や型パズルような複雑な部分が取り除かれたような書き心地となっています。WebアプリケーションやAPIサーバを書く用途にはもってこいだと思います。

参考

Discussion