Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[docs] add a vision document for Swift -> C++ interoperability #61256

Closed
wants to merge 76 commits into from

Conversation

hyp
Copy link
Contributor

@hyp hyp commented Sep 22, 2022

This is a draft Swift and C++ interoperability vision document that describes how Swift APIs are going to interoperate with C++.

Rendered markdown is here:
https://github.com/hyp/swift/blob/eng/re-road/docs/CppInteroperability/SwiftToCppInteroperabilityVisionDocument.md

Copy link
Member

@compnerd compnerd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this write up! This feels pretty comprehensive and covers all the major details that we have discussed up to this point. A few points if expanded upon would be very useful IMO which I've left some comments on as well as a number of simple editorial changes. I think that this document is in a really good shape though.


This roadmap document presents a high level overview of the “reverse” (i.e. using Swift APIs from C++) half of the C++ interoperability Swift language feature. It highlights the key principles and goals that determine how Swift APIs are exposed to C++ users. It also outlines the evolution process for this feature. This document does not present the final design for how Swift APIs get mapped to C++ language constructs. The final design of this feature will evolve as this feature goes through the Swift evolution process.

This document does not cover the “forward” (i.e. using C++ APIs from Swift) half of the C++ interoperability feature, as it’s already covered by a sibling document that can be found here: https://github.com/apple/swift/pull/60501 (https://github.com/apple/swift/pull/60501/files).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is likely better to use a file reference. I assume that the two will go in at the same time? If not, perhaps use a file reference with a footnote?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will use a file reference once the forward interop roadmap PR lands. For now I will make a it a proper markdown link though.


The generated header contains C++ class types that represent Swift’s struct , enum and class types that are exposed in the Swift module. These types provide access to methods, properties and other members using idiomatic C++ constructs, or non-idiomatic C++ constructs that allow C++ to access more functionality in a consistent manner. These types follow Swift semantics. For instance, a C++ value of Swift’s struct type gets copied and destroyed using Swift’s copy and destroy semantics. C++ also provides mechanisms that allow the programmer to move such values in a destructive way, i.e. “take” them. This ensures that C++ can interoperate with a wider range of Swift APIs and types, such as Swift’s move only types.

Protocol types also get exposed to C++. They provide access to their protocol interface to C++. The generated header also provides facilities to combine protocol types into a protocol composition type. The protocol composition type provides access to the combined protocol interface in C++.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to go into a bit more detail about the mapping? I assume that it would be an ABC except if there is an extension which provides a default implementation in which case, it would be a regular base class?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you mean by ABC, could you elaborate on that. I would prefer not to go into specific details about protocol types though, as we don't have a specific implementation design prototyped for them yet.


Safety is a top priority for the Swift programming language. Swift code expects its caller to adhere to Swift’s type rules and Swift’s memory model, regardless of whether it’s called from Swift or C++. Thus, the C++ code that calls Swift should properly enforce Swift’s expected language contracts. Such enforcement is needed to minimize the risk of Swift behaving in an unexpected manner at run-time when it’s being called from C++, as such behavior could lead to buggy program behavior or crashes that a Swift programmer would not expect to see in their Swift code. This kind of enforcement does not prevent all possible issues though. For instance, a bug in user’s C++ code could accidentally overwrite a Swift object stored on the heap, which could cause unexpected behavior or crashes in Swift code. To catch bugs like this one, the user should use other program analysis tools such as address sanitizer.

Swift expects that values of correct types are passed to Swift APIs. For instance, for calls to generic functions, Swift expects the caller to pass a value of type that conforms to all of the required generic requirements. Type safety should be enforced from C++ as well. The C++ compiler should verify that correct types are passed to the Swift APIs as they’re invoked from C++. A program that tries to pass an incorrect Swift type into a Swift function should not compile. If type verification can not be done at compile-time for such program, the program should trap when an invalid call is performed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Swift expects that values of correct types are passed to Swift APIs. For instance, for calls to generic functions, Swift expects the caller to pass a value of type that conforms to all of the required generic requirements. Type safety should be enforced from C++ as well. The C++ compiler should verify that correct types are passed to the Swift APIs as they’re invoked from C++. A program that tries to pass an incorrect Swift type into a Swift function should not compile. If type verification can not be done at compile-time for such program, the program should trap when an invalid call is performed.
Swift expects that values of correct types are passed to Swift APIs. For instance, for calls to generic functions, Swift expects the caller to pass a value of type that conforms to all of the required generic requirements. Type safety should be enforced from C++ as well. The C++ compiler should verify that correct types are passed to the Swift APIs as they’re invoked from C++. A program that tries to pass an incorrect Swift type into a Swift function should not compile. If type verification can not be done at compile-time for such a program, the program should at least trap when an invalid call is performed dynamically.

You might want to expand on this a bit, both to give an example of when it applies (e.g. when calling generic Swift code, if there are conformance or same-type requirements?) and to talk about what the trap would be like (I assume something like an assertion that can be disabled?).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be possible to check conformance or same-type requirements statically for most cases. The primary case that stands out as needing dynamic type checking is Opaque types, as it won't be possible to do static type checking when working with such types. I will talk about them.


### Safety

Safety is a top priority for the Swift programming language. Swift code expects its caller to adhere to Swift’s type rules and Swift’s memory model, regardless of whether it’s called from Swift or C++. Thus, the C++ code that calls Swift should properly enforce Swift’s expected language contracts. Such enforcement is needed to minimize the risk of Swift behaving in an unexpected manner at run-time when it’s being called from C++, as such behavior could lead to buggy program behavior or crashes that a Swift programmer would not expect to see in their Swift code. This kind of enforcement does not prevent all possible issues though. For instance, a bug in user’s C++ code could accidentally overwrite a Swift object stored on the heap, which could cause unexpected behavior or crashes in Swift code. To catch bugs like this one, the user should use other program analysis tools such as address sanitizer.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You talk about Swift's safety model here, but it might be good to call out explicitly: even though a C++ programmer is calling Swift APIs, C++'s safety model will not change. (I guess the example at the end sort of does that.) And you should probably call out what "C++'s safety model" means in this case: C++ often makes safety the user's responsibility, so users will have to be aware of Swift's safety model and will be responsible for enforcing Swift's model on the C++ side.

Copy link
Contributor Author

@hyp hyp Oct 31, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated this paragraph to something like this to incorporate your feedback:

Safety is a top priority for the Swift programming language. Swift code expects its caller to adhere to Swift’s type rules and Swift’s memory model, regardless of whether it’s called from Swift or C++. Thus, the C++ code that calls Swift should properly enforce Swift’s expected language contracts. The enforcement should be done automatically in the generated header. This kind of enforcement does not prevent all possible issues though, as it does not change C++'s safety model. C++ is unsafe by default, and the user is able to use regular C++ pointers to write to memory as if they were using Swift's UnsafeMutablePointer type. This means that bugs in C++ code can easily lead to violations of Swift's expected invariants. For instance, a bug in user’s C++ code could accidentally overwrite a Swift object stored on the heap, which could cause unexpected behavior or crashes in Swift code. The user is expected to obey Swift's memory model and type rules when calling into Swift APIs from C++, and thus they bear the ultimate responsibility for avoiding bugs like this one. The user can use certain program analysis tools, such as Address Sanitizer, to help them catch bugs that violate Swift's memory model rules.

@zoecarver
Copy link
Contributor

This is awesome, thanks Alex!

@hyp hyp changed the title [docs] add a roadmap for Swift -> C++ interoperability [docs] add a vision document for Swift -> C++ interoperability Oct 8, 2022
Copy link
Contributor

@rjmccall rjmccall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Language Workgroup met and talked about this vision today. Broadly, we're very happy with how this is shaping up. There's a few things we'd like to suggest; some of the other members have already left feedback, and this review adds some more collective feedback that we'd like to see incorporated. But overall, I think we're quite close to approving this.

Swift's support for C++ interoperability is modeled after its support for C and Objective-C interoperability. For "forward" interoperability, the embedded Clang compiler is used to directly load C++ Clang modules and translate declarations into a native Swift representation. The "forward" interoperability [vision document](https://github.com/apple/swift/pull/60501/files) provides more details about this model. For "reverse" interoperability, Swift generates a header that uses C++ language constructs to represent Swift APIs that are exposed by the Swift module. Because C++ is much more expressive and flexible than Objective-C, the generated header is able to provide representation for native Swift functions, methods, initializers, accessors and types. This allows C++ programmers to call into Swift APIs using the familiar C++ function and member function call syntax.

### C++ interoperability is opt-out not opt-in

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Language Workgroup feels that it would be valuable to center the philosophy behind this decision. It's abstractly always better for interop to work the way that you're proposing for C++: the foreign language consumes the public interface of the Swift code and makes whatever it can available without needing extra code to be emitted by the Swift compiler. Objective-C interop is specifically constrained by the need to (1) use ObjC-compatible classes and (2) put Objective-C methods directly in their method tables, which inherently requires extra code (and that code has to be in the Swift module unless you generate an extra level of ObjC object wrapper for everything, which would violate other goals). Those constraints don't apply to C++, so we do the more abstractly preferable thing.

I think you can rework the following paragraph to lead into that philosophy after the first sentence. You can then talk about things like adding support for the Swift calling convention as enabling a C++ compiler to directly interoperate with a Swift module without extra code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rewrote this section following your comment.

The permissive user model of Swift-to-C++ interoperability implies that any Swift library that can be imported into Swift can also be "imported" into C++, as long as its APIs are representable by the C++ code in the generated header. The Swift compiler can generate this header from the library's module interface file upon user's request, even when the library's author did not have such use case in mind.

## Goals

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Language Workgroup feels that it would be appropriate to lead this section with the high-level goal, explaining why this is interesting to do at all. Consider starting with something like the two paragraphs I suggested for the forward vision here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added a lead paragraph. I also liked the second paragraph you suggested in the other PR about not changing Swift's goals, but I moved it over to the "Swift language evolution and API design should be unaffected" section.

@hyp
Copy link
Contributor Author

hyp commented Dec 9, 2022

@rjmccall I updated the document following all the feedback here, please let me know how it looks now.

@hyp
Copy link
Contributor Author

hyp commented Dec 16, 2022

This PR has been superseded by swiftlang/swift-evolution#1885 as the vision document has been approved by the Swift language workgroup.

@hyp hyp closed this Dec 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ interop Feature: Interoperability with C++
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants