Foreign reference types

Not to pick nits early, but a few points here:

  1. I wasn't sure exactly what you meant by “types with reference semantics,” so I went to look at examples (since @Alvae, @shabalin, @dan-zheng, @saeta, and I have been trying to formalize value semantics, how these terms are used matters to me).
  2. I think it's unlikely these types actually have reference semantics in the way we intend the term. Expr, for example, is non-copyable, and almost every non-copyable type has value semantics by our definition: given a variable of that type, you can only change its value via operations on that variable.
  3. Giving these types from inside the Swift compiler as examples of what you mean is a bit sub-optimal for the audience. For example, I took a look at SILInstruction and while it obviously has deleted copy-assignment and delete operators, many of its properties are probably a consequence of its inheritance from llvm::ilist_node<SILInstruction>… (which has four base classes of its own!), making it tough to get a read on what you're saying. It would be better if you'd give some minimal examples.

Currently, these types cannot be imported at all, because we don't have a way to materialize them or copy them around. Foreign reference types allow these C++ "reference types" to be imported with ergonomic Swift semantics, this makes them easy to use without unsafe pointers.

I'm gonna guess that what you really mean here is “non-copyable type.” But if that's not right I hope you will enlighten me.

Foreign reference types (or custom reference types) actually have a broad set of applications beyond just C++ interop, but I won’t get into that in this post.

I agree, but I doubt the specific things you're trying to handle with this initial, limited proposal should be imported as any kind of reference type. Of course, being really sure about that requires a more rigorous nailing-down of what these types have in common…

From my point-of-view, just dealing with the non-copyable subset of types that I assume you're trying to address:

  • We'll need non-copyable types eventually in Swift, so we'd better spell out their semantics in a way that isn't interop-specific.
  • C++ types with nontrivial copy constructors should be imported as non-copyable by default. All Swift types have O(1) copy/assignment operations; allowing things like std::set<std::vector<std::string>> to be implicitly copied from Swift would introduce some frightening performance cliffs.
  • Special annotation should be available to exempt things like shared_ptr that have a nontrivial but O(1) copy, so they're copyable in Swift.
  • Non-copyable types should be passable-by-value like any other type. Passing by value doesn't imply a copy or a transfer of ownership; you can think of non-copyable types as using a guaranteed calling convention, or being passed by pointer-to-immutable.
  • @escaping annotation should be available for all parameters. You can use it whenever a non-copyable parameter value might need to outlive the callee frame; typically the parameter would have to be movable for that to work.
  • Passing a non-copyable type as an @escaping parameter ends its lifetime in the calling function. If you want to keep using it, you need to use an explicit copy (if one is available).
  • Non-copyable types are passable-by-inout like any other type.
  • Imported nontrivial types that are copyable in C++ should be given an explicit copy API by the importer.

There's an implicit contract that goes with passing by & or const& in C++: unless otherwise specified, the callee is going to assume that it has a unique reference to the value. In other words, & corresponds to Swift's inout and const& corresponds Swift's pass-by-value in the common C++ programming model. The places where these assumptions do not hold are extremely rare, in part because they require awkward hoop-jumping in specification (you can see some examples in the C++ standard library). It would be a cryin' shame if we didn't take advantage of this correspondence for interop.

4 Likes