7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[Swift]ジェネリクス関数の型が誤爆してハマった

Posted at
  • Xcode 6.1.1
  • Playground

ハマった箇所

RGB値などを処理するコードを書いていたのですが、以下のような処理が実行時にエラーとなりました。

再現を目的とした意味の無いコード
// 適当
let a: UInt8 = 230 

// 計算&UInt8の上限を超えないように抑える的な処理
let b: UInt8 = min(255, Int(a) + 50)

スクリーンショット 2015-01-23 14.49.30.png

min関数の第一引数はリテラルなので型は曖昧ですが、第二引数はInt型+リテラルなのでジェネリクス関数のmin<T>はTの型をIntと判断する だろう と思って書いたコードです。

しかし実際にはmin関数は引数をUInt8型として受け取っているようで、第二引数が230 + 50 = 280255を超えてしまっているため、オーバーフロー判定により実行時エラーとなっていると思われます。

  • 実際には、ある関数の引数へ、min関数を使った結果を渡している所で発生しました。

検証

_stdlib_getTypeNameという関数を使うと型の情報が得られるようですので、それを使ってどんな型として処理されているのかを見てみました。

まずは以下のように定数bの型を未指定にしてみます。

let a = UInt8(100)

func gen<T>(value: T) -> T {
    println(_stdlib_getTypeName(value)) // "_TtSi"
    return value
}

let b = gen(Int(a) + 50)

この時は"_TtSi"と出ますので、期待通りInt型として動いています。

しかし、bの型をいじると...

let a = UInt8(100)

func gen<T>(value: T) -> T {
    println(_stdlib_getTypeName(value)) // "_TtVSs5UInt8"
    return value
}

let b: UInt8 = gen(Int(a) + 50)

今度は"_TtVSs5UInt8"というようにUInt8型として処理していることがわかります。

左辺(代入先)の型に、ジェネリクスの型が影響を受けるって事ですね。

とはいえ、少なくともInt(a)だけはInt型のはずで、そこからどうやってUInt8型になってしまったのか、意味不明です。
勝手にコンバージョンしてるとしてもおかしな話です。

回避策

今回の件で言えばジェネリクス関数の戻り値をコンバージョンしてやれば期待通りになります。

// 適当
let a: UInt8 = 230

// 計算&UInt8の上限を超えないように抑える的な処理
let b: UInt8 = UInt8(min(255, Int(a) + 50)) // 255

実行時かつオーバーフローするケースでのみ発生するため、潜在的なバグになりえます。
お気をつけください。

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?