3
2

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 3 years have passed since last update.

[Swift] `!`(強制アンラップ)の正体はどこに…?

Posted at

はじめに

SwiftにはOptional型がありますよね。
たとえばDictionarysubscript(key: Key) -> Value? { get set }の返り値はOptional<Value>(その糖衣構文のValue?)になってますね。
値があるかどうか保証できない場合にOptional型で返すということは一般的です。

そのOptiona型のインスタンスについて、自信を持って「値がある!」と言える場合には、強制アンラップをすることがあるかと思います:

Sample.swift
let maybeInt: Int? = someFunction()
if maybeInt != nil {
  // 実際はOptional bindingを使うと思いますが、あくまで例ということで…
  print(maybeInt!)
}

この、強制アンラップ(forced unwrap)をするときの演算子ってどこで定義されているんでしょう?
それを探っていきましょう。

予想

最初は次のようなコードが標準ライブラリのどこかにあるのではないかと想像していました:

予想.swift
postfix operator !

extension Optional {
  public static postfix func !(optional: Optional) -> Wrapped {
    guard let wrapped = optional else {
      fatalError("nilだよ。")
    }
    return wrapped
  }
}

でも、そんなコードはどこにもありませんでした。
上記のコードを実行しようとしてみれば分かると思いますが、
error: postfix operator names starting with '?' or '!' are disallowed to avoid collisions with built-in unwrapping operators
というエラーが表示されると思います。
つまりSwiftのコードでは!から始まる演算子(もちろん「!だけ」も含む)を定義することはできないのです。

!の正体を探す

検証コード

検証のために次のようなコードを用意しました:

force-unwrap.swift
let _ = Int?.none!

1行でクラッシュするコードです。
今回は!の正体を知りたいだけなのでこれで十分です。

AST

とりあえずAST(抽象構文木)をダンプしてみましょう。
swiftc -dump-ast force-unwrap.swiftを実行します。

force-unwrap.swift.ast
(source_file "force-unwrap.swift"
  (top_level_code_decl range=[force-unwrap.swift:1:1 - line:1:18]
    (brace_stmt implicit range=[force-unwrap.swift:1:1 - line:1:18]
      (pattern_binding_decl range=[force-unwrap.swift:1:1 - line:1:18]
        (pattern_any type='Int')
        Original init:
        (optional_evaluation_expr implicit type='<null>'
          (force_value_expr type='Int' location=force-unwrap.swift:1:18 range=[force-unwrap.swift:1:9 - line:1:18]
            (dot_syntax_call_expr type='Int?' location=force-unwrap.swift:1:14 range=[force-unwrap.swift:1:9 - line:1:14] nothrow
              (declref_expr type='(Optional<Int>.Type) -> Optional<Int>' location=force-unwrap.swift:1:14 range=[force-unwrap.swift:1:14 - line:1:14] decl=Swift.(file).Optional.none [with (substitution_map generic_signature=<Wrapped> (substitution Wrapped -> Int))] function_ref=unapplied)
              (type_expr type='Int?.Type' location=force-unwrap.swift:1:9 range=[force-unwrap.swift:1:9 - line:1:12] typerepr='Int?'))))
        Processed init:
        (force_value_expr type='Int' location=force-unwrap.swift:1:18 range=[force-unwrap.swift:1:9 - line:1:18]
          (dot_syntax_call_expr type='Int?' location=force-unwrap.swift:1:14 range=[force-unwrap.swift:1:9 - line:1:14] nothrow
            (declref_expr type='(Optional<Int>.Type) -> Optional<Int>' location=force-unwrap.swift:1:14 range=[force-unwrap.swift:1:14 - line:1:14] decl=Swift.(file).Optional.none [with (substitution_map generic_signature=<Wrapped> (substitution Wrapped -> Int))] function_ref=unapplied)
            (type_expr type='Int?.Type' location=force-unwrap.swift:1:9 range=[force-unwrap.swift:1:9 - line:1:12] typerepr='Int?'))))
)))

なんか、もうここで「!がどこで定義されているか」の答えは出ちゃってる気がしますけどね。
!(という後置演算子)を見つけると、force_value_expr(強制アンラップした値を表す式)として解釈されるということのようです。
つまり**!はSwiftのコードで定義されている訳ではなく構文解析の段階で評価される**ということでした。

…というだけでは当たり前すぎる結果なので、もう少し掘り下げてみましょう。
だって、**「nilを強制アンラップしたらfatal errorが起きる」**という実装が見えてきていません。

まずパーサにおける!の解釈を辿るため、 https://github.com/apple/swift/blob/main/lib/Parse/ParseExpr.cpp を見てみましょう。
どうやら、まず、後置演算子の式はParser::parseExprPostfixという関数で処理されます。その関数は内部でParser::parseExprPostfixSuffixという関数を呼んでおり、!を見つけるとSyntaxKind::ForcedValueExprというノードを作ります
これがASTで見たforce_value_exprのようです。

…?
nilを強制アンラップしたときのことは(当然と言えば当然ですが)構文解析の段階では全く触れられません。
nilを強制アンラップしたらfatal errorが起きる」動作を探るため、次はSILを見てみましょう。

SIL

SIL(Swift Intermediate Language)をダンプしてみましょう。
swiftc -emit-sil force-unwrap.swiftの出番です。
長いので全部を見たい人だけ開いてみてください:

SILの出力結果
force-unwrap.swift.sil
sil_stage canonical

import Builtin
import Swift
import SwiftShims

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  %2 = metatype $@thin Optional<Int>.Type
  br bb1                                          // id: %3

bb1:                                              // Preds: bb0
  %4 = string_literal utf8 "main/force-unwrap.swift" // user: %13
  %5 = integer_literal $Builtin.Word, 23          // user: %17
  %6 = integer_literal $Builtin.Word, 18
  br bb2                                          // id: %7

bb2:                                              // Preds: bb1
  %8 = string_literal utf8 "Unexpectedly found nil while unwrapping an Optional value" // user: %10
  %9 = integer_literal $Builtin.Word, 57          // user: %12
  %10 = builtin "ptrtoint_Word"(%8 : $Builtin.RawPointer) : $Builtin.Word // user: %12
  %11 = integer_literal $Builtin.Int8, 2          // user: %12
  %12 = struct $StaticString (%10 : $Builtin.Word, %9 : $Builtin.Word, %11 : $Builtin.Int8) // user: %31
  %13 = builtin "ptrtoint_Word"(%4 : $Builtin.RawPointer) : $Builtin.Word // user: %17
  br bb3                                          // id: %14

bb3:                                              // Preds: bb2
  %15 = integer_literal $Builtin.Int8, 2          // user: %17
  br bb4                                          // id: %16

bb4:                                              // Preds: bb3
  %17 = struct $StaticString (%13 : $Builtin.Word, %5 : $Builtin.Word, %15 : $Builtin.Int8) // user: %31
  %18 = integer_literal $Builtin.Int64, 1         // user: %19
  %19 = struct $UInt (%18 : $Builtin.Int64)       // user: %31
  br bb5                                          // id: %20

bb5:                                              // Preds: bb4
  %21 = string_literal utf8 "Fatal error"         // user: %23
  %22 = integer_literal $Builtin.Word, 11         // user: %25
  %23 = builtin "ptrtoint_Word"(%21 : $Builtin.RawPointer) : $Builtin.Word // user: %25
  %24 = integer_literal $Builtin.Int8, 2          // user: %25
  %25 = struct $StaticString (%23 : $Builtin.Word, %22 : $Builtin.Word, %24 : $Builtin.Int8) // user: %31
  br bb6                                          // id: %26

bb6:                                              // Preds: bb5
  %27 = integer_literal $Builtin.Int32, 1         // user: %28
  %28 = struct $UInt32 (%27 : $Builtin.Int32)     // user: %31
  br bb7                                          // id: %29

bb7:                                              // Preds: bb6
  // function_ref _assertionFailure(_:_:file:line:flags:)
  %30 = function_ref @$ss17_assertionFailure__4file4line5flagss5NeverOs12StaticStringV_A2HSus6UInt32VtF : $@convention(thin) (StaticString, StaticString, StaticString, UInt, UInt32) -> Never // user: %31
  %31 = apply %30(%25, %12, %17, %19, %28) : $@convention(thin) (StaticString, StaticString, StaticString, UInt, UInt32) -> Never
  unreachable                                     // id: %32
} // end sil function 'main'

// _diagnoseUnexpectedNilOptional(_filenameStart:_filenameLength:_filenameIsASCII:_line:_isImplicitUnwrap:)
sil public_external [transparent] [serialized] @$ss30_diagnoseUnexpectedNilOptional14_filenameStart01_E6Length01_E7IsASCII5_line17_isImplicitUnwrapyBp_BwBi1_BwBi1_tF : $@convention(thin) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, Builtin.Word, Builtin.Int1) -> () {
// %0                                             // users: %46, %13
// %1                                             // users: %53, %20
// %2                                             // users: %47, %14
// %3                                             // users: %54, %21
// %4                                             // user: %5
bb0(%0 : $Builtin.RawPointer, %1 : $Builtin.Word, %2 : $Builtin.Int1, %3 : $Builtin.Word, %4 : $Builtin.Int1):
  cond_br %4, bb13, bb1                           // id: %5

bb1:                                              // Preds: bb0
  %6 = string_literal utf8 "Unexpectedly found nil while unwrapping an Optional value" // user: %8
  %7 = integer_literal $Builtin.Word, 57          // user: %12
  %8 = builtin "ptrtoint_Word"(%6 : $Builtin.RawPointer) : $Builtin.Word // user: %12
  br bb2                                          // id: %9

bb2:                                              // Preds: bb1
  %10 = integer_literal $Builtin.Int8, 2          // user: %12
  br bb3                                          // id: %11

bb3:                                              // Preds: bb2
  %12 = struct $StaticString (%8 : $Builtin.Word, %7 : $Builtin.Word, %10 : $Builtin.Int8) // user: %37
  %13 = builtin "ptrtoint_Word"(%0 : $Builtin.RawPointer) : $Builtin.Word // user: %20
  cond_br %2, bb5, bb4                            // id: %14

bb4:                                              // Preds: bb3
  %15 = integer_literal $Builtin.Int8, 0          // user: %16
  br bb6(%15 : $Builtin.Int8)                     // id: %16

bb5:                                              // Preds: bb3
  %17 = integer_literal $Builtin.Int8, 2          // user: %18
  br bb6(%17 : $Builtin.Int8)                     // id: %18

// %19                                            // user: %20
bb6(%19 : $Builtin.Int8):                         // Preds: bb5 bb4
  %20 = struct $StaticString (%13 : $Builtin.Word, %1 : $Builtin.Word, %19 : $Builtin.Int8) // user: %37
  %21 = builtin "zextOrBitCast_Word_Int64"(%3 : $Builtin.Word) : $Builtin.Int64 // user: %22
  %22 = struct $UInt (%21 : $Builtin.Int64)       // user: %37
  br bb7                                          // id: %23

bb7:                                              // Preds: bb6
  br bb8                                          // id: %24

bb8:                                              // Preds: bb7
  %25 = string_literal utf8 "Fatal error"         // user: %27
  %26 = integer_literal $Builtin.Word, 11         // user: %31
  %27 = builtin "ptrtoint_Word"(%25 : $Builtin.RawPointer) : $Builtin.Word // user: %31
  br bb9                                          // id: %28

bb9:                                              // Preds: bb8
  %29 = integer_literal $Builtin.Int8, 2          // user: %31
  br bb10                                         // id: %30

bb10:                                             // Preds: bb9
  %31 = struct $StaticString (%27 : $Builtin.Word, %26 : $Builtin.Word, %29 : $Builtin.Int8) // user: %37
  br bb11                                         // id: %32

bb11:                                             // Preds: bb10
  %33 = integer_literal $Builtin.Int32, 1         // user: %34
  %34 = struct $UInt32 (%33 : $Builtin.Int32)     // user: %37
  br bb12                                         // id: %35

bb12:                                             // Preds: bb11
  // function_ref _assertionFailure(_:_:file:line:flags:)
  %36 = function_ref @$ss17_assertionFailure__4file4line5flagss5NeverOs12StaticStringV_A2HSus6UInt32VtF : $@convention(thin) (StaticString, StaticString, StaticString, UInt, UInt32) -> Never // user: %37
  %37 = apply %36(%31, %12, %20, %22, %34) : $@convention(thin) (StaticString, StaticString, StaticString, UInt, UInt32) -> Never
  unreachable                                     // id: %38

bb13:                                             // Preds: bb0
  %39 = string_literal utf8 "Unexpectedly found nil while implicitly unwrapping an Optional value" // user: %41
  %40 = integer_literal $Builtin.Word, 68         // user: %45
  %41 = builtin "ptrtoint_Word"(%39 : $Builtin.RawPointer) : $Builtin.Word // user: %45
  br bb14                                         // id: %42

bb14:                                             // Preds: bb13
  %43 = integer_literal $Builtin.Int8, 2          // user: %45
  br bb15                                         // id: %44

bb15:                                             // Preds: bb14
  %45 = struct $StaticString (%41 : $Builtin.Word, %40 : $Builtin.Word, %43 : $Builtin.Int8) // user: %70
  %46 = builtin "ptrtoint_Word"(%0 : $Builtin.RawPointer) : $Builtin.Word // user: %53
  cond_br %2, bb17, bb16                          // id: %47

bb16:                                             // Preds: bb15
  %48 = integer_literal $Builtin.Int8, 0          // user: %49
  br bb18(%48 : $Builtin.Int8)                    // id: %49

bb17:                                             // Preds: bb15
  %50 = integer_literal $Builtin.Int8, 2          // user: %51
  br bb18(%50 : $Builtin.Int8)                    // id: %51

// %52                                            // user: %53
bb18(%52 : $Builtin.Int8):                        // Preds: bb17 bb16
  %53 = struct $StaticString (%46 : $Builtin.Word, %1 : $Builtin.Word, %52 : $Builtin.Int8) // user: %70
  %54 = builtin "zextOrBitCast_Word_Int64"(%3 : $Builtin.Word) : $Builtin.Int64 // user: %55
  %55 = struct $UInt (%54 : $Builtin.Int64)       // user: %70
  br bb19                                         // id: %56

bb19:                                             // Preds: bb18
  br bb20                                         // id: %57

bb20:                                             // Preds: bb19
  %58 = string_literal utf8 "Fatal error"         // user: %60
  %59 = integer_literal $Builtin.Word, 11         // user: %64
  %60 = builtin "ptrtoint_Word"(%58 : $Builtin.RawPointer) : $Builtin.Word // user: %64
  br bb21                                         // id: %61

bb21:                                             // Preds: bb20
  %62 = integer_literal $Builtin.Int8, 2          // user: %64
  br bb22                                         // id: %63

bb22:                                             // Preds: bb21
  %64 = struct $StaticString (%60 : $Builtin.Word, %59 : $Builtin.Word, %62 : $Builtin.Int8) // user: %70
  br bb23                                         // id: %65

bb23:                                             // Preds: bb22
  %66 = integer_literal $Builtin.Int32, 1         // user: %67
  %67 = struct $UInt32 (%66 : $Builtin.Int32)     // user: %70
  br bb24                                         // id: %68

bb24:                                             // Preds: bb23
  // function_ref _assertionFailure(_:_:file:line:flags:)
  %69 = function_ref @$ss17_assertionFailure__4file4line5flagss5NeverOs12StaticStringV_A2HSus6UInt32VtF : $@convention(thin) (StaticString, StaticString, StaticString, UInt, UInt32) -> Never // user: %70
  %70 = apply %69(%64, %45, %53, %55, %67) : $@convention(thin) (StaticString, StaticString, StaticString, UInt, UInt32) -> Never
  unreachable                                     // id: %71
} // end sil function '$ss30_diagnoseUnexpectedNilOptional14_filenameStart01_E6Length01_E7IsASCII5_line17_isImplicitUnwrapyBp_BwBi1_BwBi1_tF'

// _assertionFailure(_:_:file:line:flags:)
sil [noinline] [_semantics "programtermination_point"] @$ss17_assertionFailure__4file4line5flagss5NeverOs12StaticStringV_A2HSus6UInt32VtF : $@convention(thin) (StaticString, StaticString, StaticString, UInt, UInt32) -> Never



// Mappings from '#fileID' to '#filePath':
//   'main/force-unwrap.swift' => 'force-unwrap.swift'

SILの中身を見た方は分かると思いますが、ここに来ていきなり"Unexpectedly found nil while implicitly unwrapping an Optional value"というエラーメッセージを用意しています(@mainbb2:)。そしてnilをアンラップしようとしたらクラッシュするようになっています。
どうやら、構文を解析したのちSILに変換する段階で「nilを強制アンラップしたらfatal errorが起きる」動作を挿入しているようです。
これは https://github.com/apple/swift/blob/main/lib/SILGen/SILGenConvert.cpp の中の SILGenFunction::emitPreconditionOptionalHasValueという関数でおこなっています。たぶん。

そして実際のエラー処理は https://github.com/apple/swift/blob/main/stdlib/public/core/Optional.swift の中にあります。_diagnoseUnexpectedNilOptionalという関数がその処理を担っているのです。

ということで、「nilを強制アンラップしたらfatal errorが起きる」は周りまわって"Optional.swift"で定義されていたのでした。

おわりに

というわけで、!という強制アンラップの演算子を少しだけ掘り下げてみた記事でした。
実は筆者はC++が読めないので、間違いがあったらコメントをお願いします。

3
2
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
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?