You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Since upgrading from nitro 0.31.10 → 0.35.9, our iOS build fails to compile a generated Swift struct because std::vector<std::string> no longer conforms to CxxRandomAccessCollection, so the nitrogen-generated .map(...) getter doesn't compile:
error: value of type 'std.__1.vector<std.__1.basic_string<CChar, std.__1.char_traits<CChar>,
std.__1.allocator<CChar>>, std.__1.allocator<...>>>' has no member 'map'
This only happens in a module that also links a heavy third-party C++ library (in our case Tencent WCDB, a large C++ database engine vendored as a Swift framework). The exact same generated .map code compiled fine on 0.31, and still compiles fine in our other nitro modules that don't link WCDB.
We traced the trigger to the friend bool operator==(...) = default; that nitrogen started emitting on structs since 0.32.0 (#1019, described in the release notes as a "DX/convenience feature" making structs Equatable). We'd love guidance on the recommended pattern for this scenario, and whether the generated operator== could be made opt-out.
Environment
react-native-nitro-modules: 0.35.9
nitrogen: 0.35.9
React Native: 0.85.0 (new arch / Fabric / bridgeless)
Xcode 26.2, Apple Swift 6.2.3 (swiftlang-6.2.3.3.21)
C++ interop flags (from add_nitrogen_files): -cxx-interoperability-mode=default, SWIFT_OBJC_INTEROP_MODE=objcxx, CLANG_CXX_LANGUAGE_STANDARD=c++20
The module links WCDB (WCDBSwift), whose public headers contain a large amount of WINQ/ORM C++ templates and which itself uses std::vector<std::string> extensively.
What we generate
A struct with a string[] field generates (shortened):
publicextensionHealthCheckResult{@inline(__always)varissues:[String]{returnself.__issues.map({ __item inString(__item)}) // <- fails here
}}
Root cause analysis (our findings)
We diffed the 0.31 vs 0.35 generated C++ struct. The only relevant change is the defaulted equality operator added in 0.32.0 (#1019):
structHealthCheckResultfinal {
...
std::vector<std::string> issues;
friendbooloperator==(const HealthCheckResult& lhs, const HealthCheckResult& rhs) = default; // <- new since 0.32
};
Our hypothesis: operator== = default forces the compiler to instantiate std::vector<std::string>::operator==, which instantiates the __wrap_iter iterator comparison. When another C++ component in the same module (WCDB) also instantiates std::vector<std::string>, this collides (cf. the multiple definitions of symbol ...__wrap_iter... reported in swiftlang/swift#67410), and Swift then drops the automatic CxxRandomAccessCollection conformance for std::vector<std::string> in that module — so .map is no longer available.
This is consistent with swiftlang/swift#67410 ("std::vector<std::string> fails to conform to CxxRandomAccessCollection").
Evidence
Scenario
.map compiles?
nitro 0.31 (no generated operator==) + WCDB, same code
✅ yes
nitro 0.35 (generated operator==) + WCDB
❌ no
nitro 0.35, but remove the WCDB dependency from the module
✅ yes
Other 0.35 nitro modules (no heavy C++ lib) with the same string[].map
✅ yes
So: same toolchain, same WCDB — the only delta that breaks it is nitro 0.35's generated operator== co-existing with WCDB's C++ in one module.
(We did not hand-remove operator== from the generated output to fully isolate it, since generated files are regenerated and we didn't want to hand-edit them. We can patch nitrogen to confirm if that would help.)
On opt-out
Looking at nitrogen/src/syntax/c++/CppStruct.ts, the operator is emitted purely structurally:
There is currently no annotation or nitro.json option to opt out — any all-equatable struct always gets it. Since Equatable is documented as a DX/convenience feature (not a runtime requirement), it would be very helpful if it could be made opt-out for cases where it has build-breaking side effects.
Question / ask
What is the recommended way to use nitrogen-generated HybridObjects in a module that also links a heavy third-party C++ library?
Specifically:
Could nitrogen offer a way to opt out of the generated struct operator== / Equatable — e.g. a per-struct annotation or a per-module nitro.json flag? Since it's a convenience feature, making it opt-out would let projects that link heavy C++ libraries avoid forcing the std::vector comparison instantiation that triggers std::vector<std::string> fails to conform to CxxRandomAccessCollection swiftlang/swift#67410.
Is the recommended pattern instead to keep the generated specs/structs in a module that does NOT link the C++ library, and put the implementation (which uses the C++ lib) in a separate module with manual HybridObjectRegistry::registerHybridObjectConstructor(...)? Any caveats with that split?
Summary
Since upgrading from nitro 0.31.10 → 0.35.9, our iOS build fails to compile a generated Swift struct because
std::vector<std::string>no longer conforms toCxxRandomAccessCollection, so the nitrogen-generated.map(...)getter doesn't compile:This only happens in a module that also links a heavy third-party C++ library (in our case Tencent WCDB, a large C++ database engine vendored as a Swift framework). The exact same generated
.mapcode compiled fine on 0.31, and still compiles fine in our other nitro modules that don't link WCDB.We traced the trigger to the
friend bool operator==(...) = default;that nitrogen started emitting on structs since 0.32.0 (#1019, described in the release notes as a "DX/convenience feature" making structsEquatable). We'd love guidance on the recommended pattern for this scenario, and whether the generatedoperator==could be made opt-out.Environment
react-native-nitro-modules: 0.35.9nitrogen: 0.35.9use_frameworks! :linkage => :static, CocoaPodsadd_nitrogen_files):-cxx-interoperability-mode=default,SWIFT_OBJC_INTEROP_MODE=objcxx,CLANG_CXX_LANGUAGE_STANDARD=c++20WCDBSwift), whose public headers contain a large amount of WINQ/ORM C++ templates and which itself usesstd::vector<std::string>extensively.What we generate
A struct with a
string[]field generates (shortened):Root cause analysis (our findings)
We diffed the 0.31 vs 0.35 generated C++ struct. The only relevant change is the defaulted equality operator added in 0.32.0 (#1019):
Our hypothesis:
operator== = defaultforces the compiler to instantiatestd::vector<std::string>::operator==, which instantiates the__wrap_iteriterator comparison. When another C++ component in the same module (WCDB) also instantiatesstd::vector<std::string>, this collides (cf. themultiple definitions of symbol ...__wrap_iter...reported in swiftlang/swift#67410), and Swift then drops the automaticCxxRandomAccessCollectionconformance forstd::vector<std::string>in that module — so.mapis no longer available.This is consistent with swiftlang/swift#67410 ("
std::vector<std::string>fails to conform toCxxRandomAccessCollection").Evidence
.mapcompiles?operator==) + WCDB, same codeoperator==) + WCDBstring[].mapSo: same toolchain, same WCDB — the only delta that breaks it is nitro 0.35's generated
operator==co-existing with WCDB's C++ in one module.(We did not hand-remove
operator==from the generated output to fully isolate it, since generated files are regenerated and we didn't want to hand-edit them. We can patch nitrogen to confirm if that would help.)On opt-out
Looking at
nitrogen/src/syntax/c++/CppStruct.ts, the operator is emitted purely structurally:There is currently no annotation or
nitro.jsonoption to opt out — any all-equatable struct always gets it. SinceEquatableis documented as a DX/convenience feature (not a runtime requirement), it would be very helpful if it could be made opt-out for cases where it has build-breaking side effects.Question / ask
What is the recommended way to use nitrogen-generated HybridObjects in a module that also links a heavy third-party C++ library?
Specifically:
operator==/Equatable— e.g. a per-struct annotation or a per-modulenitro.jsonflag? Since it's a convenience feature, making it opt-out would let projects that link heavy C++ libraries avoid forcing thestd::vectorcomparison instantiation that triggers std::vector<std::string> fails to conform to CxxRandomAccessCollection swiftlang/swift#67410.HybridObjectRegistry::registerHybridObjectConstructor(...)? Any caveats with that split?Thanks a lot for nitro — happy to provide more details or a minimal repro if helpful.