Hello Swift Forums,
Dating back to 2022 when distributed actors are officially introduced in Swift, we made the decision of using an opaque String
value as the identifier of a remote call. In practice the string holds the mangled name of the distributed func
thunk from which type information can be extracted and function entry can be located.
The current mechanism works pretty well if we’re using Swift for both the client and the server, and we decide to encode and decode the string within the remote call envelope. Things are becoming tricky if we want to avoid that (for the security sake or for interop with other languages / existing implementations). What we really want here is referenced to as stable names in the future directions of the distributed actor runtime proposal.
Here're some attempts in pursue of this direction under the current limitation. Erlang Actor system uses @StableName
and @StableNames
macros to generate two-way mappings between stable and mangled names as properties, and uses runtime check for HasStableNames
protocol to opt-in the stable names. My own prototype, in a similar manner, utilizes the raw identifier feature coming in Swift 6.2 and a single @DistributedIdentifierMirror
macro to embed a single-way "identifier to mangled name" mapping in customMirror
for inspection from the callee's site, and use CwlDemangle to demangle and extract the raw identifier at the caller's site.
Both of the two do not address the problem of versioning, where a mangled name might change even without breaking API compatibility (I guess the latest @abi
attribute can help here). The unresolved, major problem here is that mangling in Swift is hard, and only the compiler is capable of doing it right. By using a combination of syntatic generation by macro and custom mangler implementation, we're risking being out of sync with the compiler and generating an incorrect mangled name (eg. when ABI-affecting features like raw identifiers or sending
are introduced). It's also not possible to pre-generate these names as static strings, because mangling happens after SIL while macros are applied during AST generation.
Alternative... maybe?
If we have a way to get all mangled names from the runtime reflection metadata, things could be easier. I remembered Azoy did something similar for subcommand auto discovery in the argument parser, but that’s a lot of C boilerplate and largely overkill. It also has negative impact on performance because a scan will happen at runtime, while distributed actor magic should be done at the compile time if possible.
Thinking about how executeDistributedTarget
is implemented: it first decodes and demangles the identifier in the RemoteCallTarget
, gets the function type from it with generic substitutions, then locates and executes the underlying function. If we are able to utilize a hypothetical DistributedFunctionKeyPath
to reimplement RemoteCallTarget
, we're able to ensure a safe manner for both referencing the function by mangled string (because existence can be checked during initialization) and stable name (which can be mapped to a key path losslessly). We may even gain performance boost from not demangling the identifier repeatedly.
To conclude, stable names are a vital key for enabling interoperation between Swift distributed actors and existing protocols or actors from other languages. It's also important for a stable behavior that ensures forward and backward compatibility. It's easy to express the stable names by macro or raw identifiers, but we need some improvements on RemoteCallTarget
to make it work seamlessly, at least without the need of hacking on mangling. I'm posting this as a personal summary of how this feature can be implemented and feel free to discuss what we can do next to make it into reality.
This post is a rewritten version of the original one on Swift Open Source Slack, which I will attach below, with the knowledge of Erlang Actor system.
Thanks sincerely to @ktoso for mentioning the repo and suggesting this forum post for a broader audience!
Original Post on the Swift Open Source Slack
[Help & Discussion Wanted] Hello dear distributed folks,
I was taking a try to implement a distributed actor system based on an existing RPC protocol. The current mechanism works pretty well if we’re using Swift for both the client and the server, because a RemoteCallTarget
holds a Swift-mangled function name that we can serialize and pass through the network.
Things are becoming tricky if we don’t want to carry such a string around (for the security sake or for interop with other languages / existing implementations). For the client site I managed to use swift_demangle
to extract enough information from the mangled identifier, helping me to locate the actor class and function identifier. For the server site, however, I’ve got no idea on how to mangle it back to a valid RemoteCallTarget
. I found Swift mangling rules hard to implement correctly (I tried to implement it in a macro but it turns out that the puny coding is really hard!!).
Given that what executeDistributedTarget
does first is to demangle the identifier and to get the function type, I wonder if we can introduce a variant that directly takes a distributed func
from the resolved actor. This works best with keypath, but sadly keypath doesn’t work with effective functions now, so we have to use a labeled function name + function signature (+ generic env & substitution), which can be combined with the actor instance to resolve the function.
Alternatively, if we have a way to get a mangled name and inject it into the runtime reflection metadata, things could be easier. I remembered Azoy did something similar for subcommand auto discovery in the argument parser, but that’s a lot of C boilerplate and largely overkill. A macro helper would be great, but since mangling happens after SIL we’re almost not possible to get this out during AST generation.