如果有人问你为什么在 Objective-C 中,一个参数类型为 NSArray 的方法可以传入类型为 NSMutableArray 的对象,你一定会下意识觉得这个人是个初学者。原因很简单,NSMutableArray 继承自 NSArray,子类可以替换其父类对象是面向对象思想 SOLID 中的里氏替换原则。这些基础知识和思想可能是每个面向对象设计课程中都会涉及到的。

因此,当我发现给 UnsafeBufferPointer 的构造方法 init(start: UnsafePointer<Element>?, count: Int) 传入一个类型为 UnsafeMutablePointer<Element> 的对象一切正常时并没有太过注意,直觉上认为类似于 Objective-C 的风格, UnsafeMutablePointer<Pointee> 自然是继承自 UnsafePointer<Pointee>。直到我查看 API 时才发现,不只是 UnsafePointer,所有的 Pointer 都是 struct 定义的。

0 背景知识

虽然在平时在 Swift 的使用中较少被用到,但一旦涉及到与 C 等语言的 API 调用,各种 Pointer 的出场率还是挺高的。根据指向的内容是否可变(Mutable)/指向内容是否有明确类型(Raw)/指向的是单个内容还是多个内容连续排列(Buffer),Swift 中的 Pointer 总共有 pow(2, 3) = 8 种不同的组合:

  可变? 类型明确? 连续?
UnsafePointer N Y N
UnsafeMutablePointer Y Y N
UnsafeRawPointer N N N
UnsafeMutableRawPointer Y N N
UnsafeBufferPointer N Y Y
UnsafeMutableBufferPointer Y Y Y
UnsafeRawBufferPointer N N Y
UnsafeMutableRawBufferPointer Y N Y

如果只从公开的 API 来看,其中每一个类型都是互相独立的 struct,例如 struct UnsafePointer<Pointee>,所以理论上下面的类型判断和类型转换都不成立:

var value = 10

withUnsafeMutablePointer(to: &value) { mutablePointer in
  mutablePointer is UnsafePointer<Int> // warning: Cast from 'UnsafeMutablePointer<Int>' to unrelated type 'UnsafePointer<Int>' always fails
                                      
  if let pointer = mutablePointer as? UnsafePointer<Int> { // warning: Cast from 'UnsafeMutablePointer<Int>' to unrelated type 'UnsafePointer<Int>' always fails
  }

  // call UnsafeBufferPointer.init(start: UnsafePointer<Element>?, count: Int)
  let bufferPointer = UnsafeBufferPointer<Int>(start: mutablePointer, count: 1) // UnsafeBufferPointer(start: 0x00000001055e4320, count: 1)
  print(bufferPointer.baseAddress?.pointee) // Optional(10)
}

但实际运行证明,bufferPointer 确实被成功构造了。

1. 构造方法有猫腻?

有没有可能 UnsafeBufferPointer 有两个名称相同,只是类型不同的构造方法?类似于:

UnsafeBufferPointer.init(start: UnsafePointer<Element>?, count: Int)
UnsafeBufferPointer.init(start: UnsafeMutablePointer<Element>?, count: Int)

然而从 API 看,并没有。而且同样的,即使是自定义的方法也一样有类似的机制:

func logPointer<T>(at pointer: UnsafePointer<T>) {
  print(pointer.pointee)
}

var value = 10
withUnsafeMutablePointer(to: &value) { mutablePointer in
  logPointer(at: mutablePointer) // 10
}

2. 看看编译器怎么说

既然代码能成功通过编译,说明编译的各个流程都是没问题的。靠后的 .o 文件生成、链接等流程给我们的帮助不大,或许可以通过语法树的生成过程找到些蛛丝马迹 (AST 等相关的更多信息,参考 Swift Compiler)。为了对比,同时加上一个不用 Pointer 的类似逻辑。

// main.swift
var value = 10

func log<T>(value: T) {
  print(value)
}

log(value: value)

func logPointer<T>(at pointer: UnsafePointer<T>) {
  print(pointer.pointee)
}

withUnsafeMutablePointer(to: &value) { mutablePointer in
  logPointer(at: mutablePointer)
}

在终端执行

swiftc -dump-ast main.swift

得到 AST 数据:

(source_file "main.swift"
  (top_level_code_decl range=[main.swift:1:1 - line:1:13]
    (brace_stmt implicit range=[main.swift:1:1 - line:1:13]
      (pattern_binding_decl range=[main.swift:1:1 - line:1:13]
        (pattern_named type='Int' 'value')
        Original init:
        (integer_literal_expr type='Int' location=main.swift:1:13 range=[main.swift:1:13 - line:1:13] value=10 builtin_initializer=Swift.(file).Int.init(_builtinIntegerLiteral:) initializer=**NULL**)
        Processed init:
        (integer_literal_expr type='Int' location=main.swift:1:13 range=[main.swift:1:13 - line:1:13] value=10 builtin_initializer=Swift.(file).Int.init(_builtinIntegerLiteral:) initializer=**NULL**))
))
  (var_decl range=[main.swift:1:5 - line:1:5] "value" type='Int' interface type='Int' access=internal readImpl=stored writeImpl=stored readWriteImpl=stored)
  (func_decl range=[main.swift:3:1 - line:5:1] "log(value:)" <T> interface type='<T> (value: T) -> ()' access=internal captures=(<generic> )
    (parameter_list range=[main.swift:3:12 - line:3:21]
      (parameter "value" apiName=value type='T' interface type='T'))
    (call_expr type='()' location=main.swift:4:3 range=[main.swift:4:3 - line:4:14] nothrow
      (declref_expr type='(Any..., String, String) -> ()' location=main.swift:4:3 range=[main.swift:4:3 - line:4:3] decl=Swift.(file).print(_:separator:terminator:) function_ref=single)
      (argument_list labels=_:separator:terminator:
        (argument
          (vararg_expansion_expr implicit type='Any...' location=main.swift:4:9 range=[main.swift:4:9 - line:4:9]
            (array_expr implicit type='Any...' location=main.swift:4:9 range=[main.swift:4:9 - line:4:9] initializer=**NULL**
              (erasure_expr implicit type='Any' location=main.swift:4:9 range=[main.swift:4:9 - line:4:9]
                (declref_expr type='T' location=main.swift:4:9 range=[main.swift:4:9 - line:4:9] decl=main.(file).log(value:).value@main.swift:3:13 function_ref=unapplied)))))
        (argument label=separator
          (default_argument_expr implicit type='String' location=main.swift:4:8 range=[main.swift:4:8 - line:4:8] default_args_owner=Swift.(file).print(_:separator:terminator:) param=1))
        (argument label=terminator
          (default_argument_expr implicit type='String' location=main.swift:4:8 range=[main.swift:4:8 - line:4:8] default_args_owner=Swift.(file).print(_:separator:terminator:) param=2))
      )))
  (top_level_code_decl range=[main.swift:7:1 - line:7:17]
    (brace_stmt implicit range=[main.swift:7:1 - line:7:17]
      (call_expr type='()' location=main.swift:7:1 range=[main.swift:7:1 - line:7:17] nothrow
        (declref_expr type='(Int) -> ()' location=main.swift:7:1 range=[main.swift:7:1 - line:7:1] decl=main.(file).log(value:)@main.swift:3:6 [with (substitution_map generic_signature=<T> (substitution T -> Int))] function_ref=single)
        (argument_list labels=value:
          (argument label=value
            (load_expr implicit type='Int' location=main.swift:7:12 range=[main.swift:7:12 - line:7:12]
              (declref_expr type='@lvalue Int' location=main.swift:7:12 range=[main.swift:7:12 - line:7:12] decl=main.(file).value@main.swift:1:5 function_ref=unapplied)))
        ))))
  (func_decl range=[main.swift:9:1 - line:11:1] "logPointer(at:)" <T> interface type='<T> (at: UnsafePointer<T>) -> ()' access=internal captures=(<generic> )
    (parameter_list range=[main.swift:9:19 - line:9:48]
      (parameter "pointer" apiName=at type='UnsafePointer<T>' interface type='UnsafePointer<T>'))
    (call_expr type='()' location=main.swift:10:3 range=[main.swift:10:3 - line:10:24] nothrow
      (declref_expr type='(Any..., String, String) -> ()' location=main.swift:10:3 range=[main.swift:10:3 - line:10:3] decl=Swift.(file).print(_:separator:terminator:) function_ref=single)
      (argument_list labels=_:separator:terminator:
        (argument
          (vararg_expansion_expr implicit type='Any...' location=main.swift:10:9 range=[main.swift:10:9 - line:10:17]
            (array_expr implicit type='Any...' location=main.swift:10:9 range=[main.swift:10:9 - line:10:17] initializer=**NULL**
              (erasure_expr implicit type='Any' location=main.swift:10:17 range=[main.swift:10:9 - line:10:17]
                (member_ref_expr type='T' location=main.swift:10:17 range=[main.swift:10:9 - line:10:17] decl=Swift.(file).UnsafePointer.pointee [with (substitution_map generic_signature=<Pointee> (substitution Pointee -> T))]
                  (declref_expr type='UnsafePointer<T>' location=main.swift:10:9 range=[main.swift:10:9 - line:10:9] decl=main.(file).logPointer(at:).pointer@main.swift:9:23 function_ref=unapplied))))))
        (argument label=separator
          (default_argument_expr implicit type='String' location=main.swift:10:8 range=[main.swift:10:8 - line:10:8] default_args_owner=Swift.(file).print(_:separator:terminator:) param=1))
        (argument label=terminator
          (default_argument_expr implicit type='String' location=main.swift:10:8 range=[main.swift:10:8 - line:10:8] default_args_owner=Swift.(file).print(_:separator:terminator:) param=2))
      )))
  (top_level_code_decl range=[main.swift:13:1 - line:15:1]
    (brace_stmt implicit range=[main.swift:13:1 - line:15:1]
      (call_expr type='()' location=main.swift:13:1 range=[main.swift:13:1 - line:15:1] nothrow
        (declref_expr type='(inout Int, (UnsafeMutablePointer<Int>) throws -> ()) throws -> ()' location=main.swift:13:1 range=[main.swift:13:1 - line:13:1] decl=Swift.(file).withUnsafeMutablePointer(to:_:) [with (substitution_map generic_signature=<T, Result> (substitution T -> Int) (substitution Result -> ()))] function_ref=single)
        (argument_list labels=to:_:
          (argument label=to inout
            (inout_expr type='inout Int' location=main.swift:13:30 range=[main.swift:13:30 - line:13:31]
              (declref_expr type='@lvalue Int' location=main.swift:13:31 range=[main.swift:13:31 - line:13:31] decl=main.(file).value@main.swift:1:5 function_ref=unapplied)))
          (argument
            (function_conversion_expr implicit type='(UnsafeMutablePointer<Int>) throws -> ()' location=main.swift:13:38 range=[main.swift:13:38 - line:15:1]
              (closure_expr type='(UnsafeMutablePointer<Int>) -> ()' location=main.swift:13:38 range=[main.swift:13:38 - line:15:1] discriminator=0 single-expression
                (parameter_list range=[main.swift:13:40 - line:13:40]
                  (parameter "mutablePointer" type='UnsafeMutablePointer<Int>' interface type='UnsafeMutablePointer<Int>'))
                (brace_stmt range=[main.swift:13:38 - line:15:1]
                  (return_stmt implicit range=[main.swift:14:3 - line:14:32]
                    (call_expr type='()' location=main.swift:14:3 range=[main.swift:14:3 - line:14:32] nothrow
                      (declref_expr type='(UnsafePointer<Int>) -> ()' location=main.swift:14:3 range=[main.swift:14:3 - line:14:3] decl=main.(file).logPointer(at:)@main.swift:9:6 [with (substitution_map generic_signature=<T> (substitution T -> Int))] function_ref=single)
                      (argument_list labels=at:
                        (argument label=at
                          (pointer_to_pointer implicit type='UnsafePointer<Int>' location=main.swift:14:18 range=[main.swift:14:18 - line:14:18]
                            (declref_expr type='UnsafeMutablePointer<Int>' location=main.swift:14:18 range=[main.swift:14:18 - line:14:18] decl=main.(file).top-level code.explicit closure discriminator=0.mutablePointer@main.swift:13:40 function_ref=unapplied)))
                      )))))))
        )))))

乍一看确实容易被长度吓住,但两个版本的同一段大致结构是类似的,我们只看两版本 log 方法的调用部分:

// func log<T>(value: T)
(call_expr type='()' location=main.swift:7:1 range=[main.swift:7:1 - line:7:17] nothrow
        (declref_expr type='(Int) -> ()' location=main.swift:7:1 range=[main.swift:7:1 - line:7:1] decl=main.(file).log(value:)@main.swift:3:6 [with (substitution_map generic_signature=<T> (substitution T -> Int))] function_ref=single)
        (argument_list labels=value:
          (argument label=value
            (load_expr implicit type='Int' location=main.swift:7:12 range=[main.swift:7:12 - line:7:12]
              (declref_expr type='@lvalue Int' location=main.swift:7:12 range=[main.swift:7:12 - line:7:12] decl=main.(file).value@main.swift:1:5 function_ref=unapplied)))
        ))


// func logPointer<T>(at pointer: UnsafePointer<T>)
(call_expr type='()' location=main.swift:14:3 range=[main.swift:14:3 - line:14:32] nothrow
                      (declref_expr type='(UnsafePointer<Int>) -> ()' location=main.swift:14:3 range=[main.swift:14:3 - line:14:3] decl=main.(file).logPointer(at:)@main.swift:9:6 [with (substitution_map generic_signature=<T> (substitution T -> Int))] function_ref=single)
                      (argument_list labels=at:
                        (argument label=at
                          (pointer_to_pointer implicit type='UnsafePointer<Int>' location=main.swift:14:18 range=[main.swift:14:18 - line:14:18]
                            (declref_expr type='UnsafeMutablePointer<Int>' location=main.swift:14:18 range=[main.swift:14:18 - line:14:18] decl=main.(file).top-level code.explicit closure discriminator=0.mutablePointer@main.swift:13:40 function_ref=unapplied)))
                      ))

再具体点,只看参数部分:

// func log<T>(value: T)
(argument label=value
            (load_expr implicit type='Int' location=main.swift:7:12 range=[main.swift:7:12 - line:7:12]
              (declref_expr type='@lvalue Int' location=main.swift:7:12 range=[main.swift:7:12 - line:7:12] decl=main.(file).value@main.swift:1:5 function_ref=unapplied)))
              
              
// func logPointer<T>(at pointer: UnsafePointer<T>)
(argument label=at
                          (pointer_to_pointer implicit type='UnsafePointer<Int>' location=main.swift:14:18 range=[main.swift:14:18 - line:14:18]
                            (declref_expr type='UnsafeMutablePointer<Int>' location=main.swift:14:18 range=[main.swift:14:18 - line:14:18] decl=main.(file).top-level code.explicit closure discriminator=0.mutablePointer@main.swift:13:40 function_ref=unapplied)))

pointer_to_pointer 这个操作看上去就是问题的关键了,似乎就是这一步将原本的 UnsafeMutablePointer<Int> 转成了方法所需的 UnsafePointer<Int>。我们就以此为关键词在 Swift 的仓库中搜索

Expr.h 的定义中我们可以看出,PointerToPointer 隶属于隐式转换操作:

/// Convert a pointer to a different kind of pointer.
class PointerToPointerExpr : public ImplicitConversionExpr {
public:
  PointerToPointerExpr(Expr *subExpr, Type ty)
    : ImplicitConversionExpr(ExprKind::PointerToPointer, subExpr, ty) {}
  
  static bool classof(const Expr *E) {
    return E->getKind() == ExprKind::PointerToPointer;
  }
};

KnownDecls.def 中可以看出,这个操作实际上调用的是名为 _convertPointerToPointerArgument 的私有方法:

FUNC_DECL(ConvertPointerToPointerArgument,
          "_convertPointerToPointerArgument")

我们继续查找这个方法,可以找到这是定义在 Pointer.swift 文件中的全局方法:

/// Derive a pointer argument from a convertible pointer type.
@_transparent
public // COMPILER_INTRINSIC
func _convertPointerToPointerArgument<
  FromPointer: _Pointer,
  ToPointer: _Pointer
>(_ from: FromPointer) -> ToPointer {
  return ToPointer(from._rawValue)
}

3. 私有协议 _Pointer

查到这里答案就呼之欲出了,看似没有任何关系的各种 Pointer,实际上都实现了私有协议 _Pointer。再准确点说,非 Buffer 的四种指针实现了这个协议。

public protocol _Pointer
: Hashable, Strideable, CustomDebugStringConvertible, _CustomReflectableOrNone {
  /// A type that represents the distance between two pointers.
  typealias Distance = Int
  
  associatedtype Pointee

  /// The underlying raw pointer value.
  var _rawValue: Builtin.RawPointer { get }

  /// Creates a pointer from a raw value.
  init(_ _rawValue: Builtin.RawPointer)
}

本质上这四种指针都只是对私有类型 Builtin.RawPointer 的包装,因此在 _convertPointerToPointerArgument 方法的实现中,也是通过 from._rawValue 构造了新的具体 Pointer 类型。

没有实现 _Pointer 协议的四种 BufferPointer 自然就没有类似的隐式转换机制了。本质上,BufferPointer 只是包装了一个 Pointer 作为 baseAddress 的容器,存在的价值更多在于实现了 Collection 相关协议,便于像操作集合一样操作连续的指针。

4. 特殊情况的处理

4.1 反向转换

既然都是对于 Builtin.RawPointer 的包装,理论上可以自由地互相转换。但稍微考虑一下,把可变指针当做不可变指针使用并无不妥,但反过来对于不可变指针做修改就是一个很有风险的操作了,Swift 如何避免出现这种情况下的隐式转换呢?

答案是在语义分析时,只针对特定的类型使用 pointer_to_pointer 规则

PointerTypeKind type1PointerKind;
bool type1IsPointer{
    unwrappedType1->getAnyPointerElementType(type1PointerKind)};
bool optionalityMatches = !type1IsOptional || type2IsOptional;
if (type1IsPointer && optionalityMatches) {
  if (type1PointerKind == PTK_UnsafeMutablePointer) {
    // Favor an UnsafeMutablePointer-to-UnsafeMutablePointer
    // conversion.
    if (type1PointerKind != pointerKind)
      increaseScore(ScoreKind::SK_ValueToPointerConversion);
    conversionsOrFixes.push_back(
      ConversionRestrictionKind::PointerToPointer);
  }
  // UnsafeMutableRawPointer -> UnsafeRawPointer
  else if (type1PointerKind == PTK_UnsafeMutableRawPointer &&
           pointerKind == PTK_UnsafeRawPointer) {
    if (type1PointerKind != pointerKind)
      increaseScore(ScoreKind::SK_ValueToPointerConversion);
    conversionsOrFixes.push_back(
      ConversionRestrictionKind::PointerToPointer);              
  }
}

4.2 Pointee 类型不同

Pointer 除了自身的类型,对于明确指向内容类型的 Pointer 也还有着关联类型 Pointee,那么对于 Pointee 类型无关的情况是怎么处理的呢?

答案是 Pointee 的类型同样要参与能否转换的判断

static llvm::PointerIntPair<Type, 3, unsigned>
getBaseTypeForPointer(TypeBase *type) {
  unsigned unwrapCount = 0;
  while (auto objectTy = type->getOptionalObjectType()) {
    type = objectTy.getPointer();
    ++unwrapCount;
  }

  auto pointeeTy = type->getAnyPointerElementType();
  assert(pointeeTy);
  return {pointeeTy, unwrapCount};
}

// line 11285
// T <p U ===> UnsafeMutablePointer<T> <a UnsafeMutablePointer<U>
case ConversionRestrictionKind::PointerToPointer: {
  auto t1 = type1->getDesugaredType();
  auto t2 = type2->getDesugaredType();

  auto ptr1 = getBaseTypeForPointer(t1);
  auto ptr2 = getBaseTypeForPointer(t2);

  return matchPointerBaseTypes(ptr1, ptr2);
}

5. 隐式转换虽然不显眼,其实很常见

Swift 作为一个静态类型+强类型的语言,很多情况下如果没有隐式转换的帮助,强类型就不再是安全的保证而是编码的负担,举个例子:

func foo(i: Int?) {
  print(i ?? -1)
}

var value = 10
foo(i: value)

// without implicit conversion 
foo(i: Int?(value))
// or
foo(i: .some(value))

foo(i:) 函数需要的参数类型是 Optional<Int> ,但将类型为 Intvalue 传入也并无问题,这其中就是名为自动装包(inject_into_optional)的隐式转换在发挥作用:

(inject_into_optional implicit type='Int?' location=main.swift:6:8 range=[main.swift:6:8 - line:6:8]
              (load_expr implicit type='Int' location=main.swift:6:8 range=[main.swift:6:8 - line:6:8]
                (declref_expr type='@lvalue Int' location=main.swift:6:8 range=[main.swift:6:8 - line:6:8] decl=main.(file).value@main.swift:5:5 function_ref=unapplied)))

同样的,实现基于类继承的里氏替换同样是一个隐式转换操作(derived_to_base_expr):

class Sup {
  var value: Int = 0
}

class Sub: Sup { }

func foo(i: Sup) {
  print(sub)
}

let sub = Sub()
sub.value = 10
foo(i: sub)

// AST
(derived_to_base_expr implicit type='Sup' location=main.swift:15:8 range=[main.swift:15:8 - line:15:8]
              (declref_expr type='Sub' location=main.swift:15:8 range=[main.swift:15:8 - line:15:8] decl=main.(file).sub@main.swift:13:5 function_ref=unapplied))

block 与 function 之间的转换也是(function_conversion_expr):

func foo(block: (Int) -> Int) {
  print(block(10))
}

func addOne(value: Int) -> Int {
  return value + 1
}

foo(block: addOne(value:))

// AST
(call_expr type='()' location=main.swift:9:1 range=[main.swift:9:1 - line:9:26] nothrow
        (declref_expr type='((Int) -> Int) -> ()' location=main.swift:9:1 range=[main.swift:9:1 - line:9:1] decl=main.(file).foo(block:)@main.swift:1:6 function_ref=single)
        (argument_list labels=block:
          (argument label=block
            (function_conversion_expr implicit type='(Int) -> Int' location=main.swift:9:12 range=[main.swift:9:12 - line:9:25]
              (declref_expr type='(Int) -> Int' location=main.swift:9:12 range=[main.swift:9:12 - line:9:25] decl=main.(file).addOne(value:)@main.swift:5:6 function_ref=compound)))
        ))

所有支持的隐式转换操作都可以在 ExprNodes.def 的定义中找到:

EXPR(Load, ImplicitConversionExpr)
EXPR(ABISafeConversion, ImplicitConversionExpr)
EXPR(DestructureTuple, ImplicitConversionExpr)
EXPR(UnresolvedTypeConversion, ImplicitConversionExpr)
EXPR(FunctionConversion, ImplicitConversionExpr)
EXPR(CovariantFunctionConversion, ImplicitConversionExpr)
EXPR(CovariantReturnConversion, ImplicitConversionExpr)
EXPR(MetatypeConversion, ImplicitConversionExpr)
EXPR(CollectionUpcastConversion, ImplicitConversionExpr)
EXPR(Erasure, ImplicitConversionExpr)
EXPR(AnyHashableErasure, ImplicitConversionExpr)
EXPR(BridgeToObjC, ImplicitConversionExpr)
EXPR(BridgeFromObjC, ImplicitConversionExpr)
EXPR(ConditionalBridgeFromObjC, ImplicitConversionExpr)
EXPR(DerivedToBase, ImplicitConversionExpr)
EXPR(ArchetypeToSuper, ImplicitConversionExpr)
EXPR(InjectIntoOptional, ImplicitConversionExpr)
EXPR(ClassMetatypeToObject, ImplicitConversionExpr)
EXPR(ExistentialMetatypeToObject, ImplicitConversionExpr)
EXPR(ProtocolMetatypeToObject, ImplicitConversionExpr)
EXPR(InOutToPointer, ImplicitConversionExpr)
EXPR(ArrayToPointer, ImplicitConversionExpr)
EXPR(StringToPointer, ImplicitConversionExpr)
EXPR(PointerToPointer, ImplicitConversionExpr)
EXPR(ForeignObjectConversion, ImplicitConversionExpr)
EXPR(UnevaluatedInstance, ImplicitConversionExpr)
EXPR(UnderlyingToOpaque, ImplicitConversionExpr)
EXPR(DifferentiableFunction, ImplicitConversionExpr)
EXPR(LinearFunction, ImplicitConversionExpr)
EXPR(DifferentiableFunctionExtractOriginal, ImplicitConversionExpr)
EXPR(LinearFunctionExtractOriginal, ImplicitConversionExpr)
EXPR(LinearToDifferentiableFunction, ImplicitConversionExpr)
EXPR(ReifyPack, ImplicitConversionExpr)