Creating `Dictionary<Key, Value>` from C++

Hey all!

I’m trying to construct a large Dictionary<Key, Value> from C++.
Unfortunately Dictionary<Key, Value> uses generic constraints;

// from Swift's source code:
@frozen public struct Dictionary<Key, Value> where Key : Hashable {
  ...

..which causes the type to not be available in C++ using the Swift/C++ interop.

I created a feature request to support generic constraints here: [cxx-interop] Support generic constraints (e.g. `struct Dictionary<Key, Value> where Key : Hashable`) · Issue #86172 · swiftlang/swift · GitHub
..and I also created a feature request to support swift::Dictionary<K, V> here: [cxx-interop] Support Swift's `Dictionary<K, V>` in C++ (`swift::Dictionary<K, V>`?) · Issue #76134 · swiftlang/swift · GitHub
..since the latter might be "easier" to implement than allowing full generic constraints in C++ (as that implies some protocol support, even if it's just limited to Hashable or Equatable)

Anyways, I wanted to ask if anyone has achieved something like this before?

I tried to use AnyHashable, which is also not exposed to C++, I tried to manually specialize each key type (e.g. struct StringDictionary<Value>, struct IntDictionary<Value>, ...) but this is not really scalable, and still doesn't allow me to access the actual Dictionary value.

For now my best workaround is to box the type (Unmanaged<...>) and create generic helpers in Swift, but this still requires a special Swift handler to unwrap the type.

Please show a minimal example of what you are currently doing on both sides (in C and in Swift).

I am building a code-generator that bridges values from a JavaScript environment (which is in C++) to Swift.
Essentially the user defines his types in TypeScript, which then generates these Swift interfaces for him:

public protocol MyClass {
  func a(dict: Dictionary<String, String>)
  func b(dict: Dictionary<Int, Double>)
  // ...
}

..the user is free to choose whatever type for Key or Value he wants, so I cannot just specialize only Dictionary<String, String> or Dictionary<Int, Double>. It should be generic, to some extent at least - I can still generate bridging code.

I then generate bridging code to allow calling those methods from C++:

public class MyClass_cxx {
  private let implementation: any MyClass
  func a(dict: Dictionary<String, String>) {
    implementation.a(dict: dict)
  }
  func b(dict: Dictionary<Int, Double>) {
    implementation.b(dict: dict)
  }
}

From C++, I then call this:

class MyClass {
public:
  void a(jsi::Runtime&, jsi::Value dict) {
    swiftPart.a(/* how do I create a Dictionary<...> here? */);
  }
  void b(jsi::Runtime&, jsi::Value dict) {
    swiftPart.b(/* how do I create a Dictionary<...> here? */);
  }
private:
  MySwift::MyClass_cxx swiftPart;
};

So these are the workarounds I considered;

Boxing it to void*

I generate a Swift helper to box a Dictionary<String, String> to void*:

public class DictionaryStringStringHelper {
 class Boxed {
    let dictionary: Dictionary<String, String>
    init(dict: Dictionary<String, String>) { self.dictionary = dict }
  }
  public static func createDict() -> UnsafeMutableRawPointer {
    let boxed = Boxed(dict: Dictionary<String, String>())
    return Unmanaged.passRetained(boxed).toOpaque()
  }
  public static func unbox(pointer: UnsafeMutableRawPointer) -> Dictionary<String, String> {
    return Unmanaged< Boxed >.fromOpaque(pointer).takeRetainedValue()
  }
  public static func set(in: UnsafeMutableRawPointer, key: String, value: String) {
    // implement
  }
  // implement get, contains, getAllKeys, ...
}

Then my MyClass_cxx helper operates on void*:

public class MyClass_cxx {
  private let implementation: any MyClass
  func a(dict: UnsafeMutableRawPointer) {
    implementation.a(dict: DictionaryStringStringHelper.unbox(pointer: dict))
  }
  func b(dict: UnsafeMutableRawPointer) {
    implementation.b(dict: DictionaryStringStringHelper.unbox(pointer: dict))
  }
}

This works, but requires me to generate a bunch of code (e.g. for setters, getters, and other accessors) - and boxing seems to be quite annoying.

Using std::unordered_map

I can create an std::unordered_map in C++, then my MyClass_cxx can take a std.unordered_map_string_string (specialized template), and then I do an additional loop/conversion/copy in Swift to a Dictionary<String, String>.

This works too, but doesn't scale that good with really large Dictionaries because of the additional copy/loops.

Any other ideas?