[Swift] 関数で String と Substring のどちらも受けとりたい

     
String と Substring のどちらも受けとることができる関数の作り方を説明します

環境&対象

以下の環境で動作確認を行なっています。

  • macOS Monterey 12.3 Beta
  • Xcode 13.2.1
  • iOS 15.2

String と Substring

String は、文字を表現する型です。

関数の引数等にも、よく使われる型です。

その String に対して操作を行うと、Substring になる時があります。

例えば、次のコードでは、str を(カンマ区切りで)分解した文字列を tokens に保持すしています。
この tokens には、Substring 型のデータが保存されています。

Substring 型のデータを String として扱いたい時には、Substring を引数として受け取る String initializer を使うと、String 型のデータを生成することもできます。


let str = "Hello,world"
let tokens = str.split(separator: ",")
print(tokens)  // -> ["Hello", "world"]
print(type(of: tokens[0])  // -> Substring
let token0 = String(tokens[0])
print(type(of: token0) // -> String

以下の記事でも 説明しています。
Swift[Swift] Swiftでも文字列操作 Substringの扱い方

Substring を引数として受け取る

引き渡し先の関数が、String しか受け付けない時はしょうがないのですが、ただ 参照するだけのケースでは、わざわざ String を生成して渡すのはすこし無駄な気がします。

Swift のライブラリ等は、Substring でも受け付けることができる関数が多く定義されています。

ですが、自分でそのような関数を作ろうとして Substring を受け付ける関数を作っても期待通りには動きません・・・・

実際に作ってみると以下のようなエラーとなる箇所が発生します。


func acceptStringAndSubstring(anyStr: SubString) { }

Error : cannot convert value of type 'String' to expected argument type 'Substring'

Substring を引数として呼び出す箇所はうまく動くのですが、String を引数として呼び出す箇所では 上記のエラーが発生します。意味は、"String を渡されたが、引数として必要な Substring に変換できない" ということです。

つまり、引数の型として、Substring を指定してしまうと、String が受け取れないということです。なんとなく、String と Substring は、自動相互変換されそうですが、そうではないということでした。

Note: 複数の関数を定義して それぞれが String と Substring を受け取るようにするのも一案ですが、スマートではありません。

String と Substring のどちらもが準拠する Protocol として StringProtocol があります
1つの関数で String と Substring のいずれも受け取れるようにするためには、この StringProtocl を使用して定義する必要があります。 → ですがエラーとなります。


func acceptStringAndSubstring(anyStr: StringProtocol) { }

Error: protocol 'StringProtocol' can only be used as a generic constraint because it has Self or associated type requirements

上記のエラーからもわかりますが、Swift では、Protocol を引数の型に直接指定することはできません。

使用するためには、Generics を使用して定義する必要があります。

具体的には 以下のように定義することで、String, Substring のどちらもが準拠する StringProtocol を引数の型として指定できることになります。


func acceptStringAndSubstring<T>(anyStr: T) where T: StringProtocol  { }

上記のように定義することで、String, Substring いずれの型のデータであっても受け付けることが可能となります。

まとめ

String, Substring のどちらも受け取れるような関数を定義する方法
  • String, Substring のどちらもが準拠する StringProtocol を型に指定する
  • Generics を指定して、StringProtocol を型に指定する必要がある

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

コメントを残す

メールアドレスが公開されることはありません。