Skip to content

Commit 54c5e11

Browse files
committed
First big round of updates
- Update pymetabind - Complete nanobind documentation of the new feature - Change "foreign" to "interop" in some places so that the word "foreign" is more consistently used for the other framework rather than the information exchange between them - Allow enum types to participate in interop - Allow nanobind to register implicit conversions from foreign types to nanobind types
1 parent 6769a72 commit 54c5e11

File tree

15 files changed

+396
-127
lines changed

15 files changed

+396
-127
lines changed

cmake/nanobind-config.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ function (nanobind_build_library TARGET_NAME)
238238
endif()
239239

240240
if (TARGET_NAME MATCHES "-local")
241-
target_compile_definitions(${TARGET_NAME} PRIVATE NB_DISABLE_FOREIGN)
241+
target_compile_definitions(${TARGET_NAME} PRIVATE NB_DISABLE_INTEROP)
242242
endif()
243243

244244
# Nanobind performs many assertion checks -- detailed error messages aren't

docs/api_cmake.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,9 @@ The high-level interface consists of just one CMake command:
111111
If this explanation sounds confusing, then you can ignore it. See the
112112
detailed description below for more information on this step.
113113
* - ``NO_INTEROP``
114-
- Remove support for interoperability with other Python binding
115-
frameworks. If you don't need it in your environment, this offers
116-
a minor performance and code size benefit.
114+
- Remove support for :ref:`interoperability with other Python binding
115+
frameworks <interop>`. If you don't need it in your environment, this
116+
offers a minor performance and code size benefit.
117117

118118
:cmake:command:`nanobind_add_module` performs the following
119119
steps to produce bindings.

docs/api_core.rst

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3130,12 +3130,24 @@ Global flags
31303130
functions are still alive when the Python interpreter shuts down. Call this
31313131
function to disable or re-enable leak warnings.
31323132

3133+
The configuration affected by this function is global to a :ref:`nanobind
3134+
domain <type-visibility>`. If you use it, you are encouraged to isolate your
3135+
extension from others by passing the ``NB_DOMAIN`` parameter to the
3136+
:cmake:command:`nanobind_add_module()` CMake command, so that your choices
3137+
don't need to impact unrelated extensions.
3138+
31333139
.. cpp:function:: void set_implicit_cast_warnings(bool value) noexcept
31343140

31353141
By default, nanobind loudly complains when it attempts to perform an
31363142
implicit conversion, and when that conversion is not successful. Call this
31373143
function to disable or re-enable the warnings.
31383144

3145+
The configuration affected by this function is global to a :ref:`nanobind
3146+
domain <type-visibility>`. If you use it, you are encouraged to isolate your
3147+
extension from others by passing the ``NB_DOMAIN`` parameter to the
3148+
:cmake:command:`nanobind_add_module()` CMake command, so that your choices
3149+
don't need to impact unrelated extensions.
3150+
31393151
.. cpp:function:: inline bool is_alive() noexcept
31403152

31413153
The function returns ``true`` when nanobind is initialized and ready for
@@ -3144,6 +3156,85 @@ Global flags
31443156
this liveness status can be useful to avoid operations that are illegal in
31453157
the latter context.
31463158

3159+
Interoperability
3160+
----------------
3161+
3162+
See the :ref:`separate interoperability documentation <interop>` for important
3163+
additional information and caveats about this feature.
3164+
3165+
.. cpp:function:: void interoperate_by_default(bool export_all = true, bool import_all = true)
3166+
3167+
Configure whether this :ref:`nanobind domain <type-visibility>` should exchange
3168+
type information with other binding libraries (and other nanobind domains)
3169+
so that functions bound in one can accept and return objects whose types are
3170+
bound in another. The default, if :cpp:func:`interoperate_by_default` is not
3171+
called, is to exchange such information only when requested on a per-type
3172+
basis via calls to :cpp:func:`import_for_interop` or
3173+
:cpp:func:`export_for_interop`.
3174+
3175+
By specifying arguments to :cpp:func:`interoperate_by_default`, it is
3176+
possible to separately configure whether to enable automatic sharing of
3177+
our types with others (*export_all*) or automatic use of types shared by
3178+
others (*import_all*). Once either type of automatic sharing is enabled,
3179+
it remains enabled for the lifetime of the Python interpreter.
3180+
3181+
Automatic export is equivalent to calling :cpp:func:`export_for_interop` on
3182+
each type produced by a :cpp:class:`nb::class_` or :cpp:struct:`nb::enum_`
3183+
binding statement in this nanobind domain.
3184+
3185+
Automatic import is equivalent to calling :cpp:func:`import_for_interop` on
3186+
each type exported by a different binding library or nanobind domain, as
3187+
long as that library is written in C++ and uses a compatible platform ABI.
3188+
In order to interoperate with a type binding that was created in another
3189+
language, including pure C, you must still make an explicit call to
3190+
:cpp:func:`import_for_interop`, providing a template argument so that
3191+
nanobind can tell which C++ type should be associated with it.
3192+
3193+
Enabling automatic export or import affects both types that have already been
3194+
bound and types that are yet to be bound.
3195+
3196+
The configuration affected by this function is global to a nanobind
3197+
domain. If you use it, you are encouraged to isolate your extension from
3198+
others by passing the ``NB_DOMAIN`` parameter to the
3199+
:cmake:command:`nanobind_add_module()` CMake command, so that your choices
3200+
don't need to impact unrelated extensions.
3201+
3202+
.. cpp:function:: template <typename T = void> void import_for_interop(handle type)
3203+
3204+
Make the Python type object *type*, which was bound using a different binding
3205+
framework (or different nanobind domain) and then exported using a facility
3206+
like our :cpp:func:`export_for_interop`, be available so that functions bound
3207+
in this nanobind domain can accept and return its instances using the
3208+
C++ type ``T``, or the C++ type that was specified when *type* was bound.
3209+
3210+
If no template argument is specified, then *type* must have been bound by
3211+
another C++ binding library that uses the same :ref:`platform ABI
3212+
<type-visibility>` as us; the C++ type will be determined by inspecting its
3213+
binding.
3214+
3215+
If a template argument is specified, then nanobind associates that C++ type
3216+
with the given Python type, trusting rather than verifying that they match.
3217+
The C++ type ``T`` must perfectly match the memory layout and ABI of the
3218+
structure used by whoever bound the Python *type*.
3219+
This is the only way to import types written in other languages than C++
3220+
(including those in plain C), since nanobind needs a ``std::type_info`` for
3221+
its type lookups and non-C++ bindings don't provide those directly.
3222+
3223+
Repeatedly importing the same Python type as the same C++ type is idempotent.
3224+
Importing the same Python type as multiple different C++ types will fail.
3225+
3226+
A descriptive C++ exception will be thrown if the import fails.
3227+
3228+
.. cpp:function:: void export_for_interop(handle type)
3229+
3230+
Make the Python type object *type*, which was bound in this nanobind domain,
3231+
be available for import by other binding libraries and other nanobind
3232+
domains. If they do so, then their bound functions will be able to accept
3233+
and return instances of *type*.
3234+
3235+
Repeatedly exporting the same type is idempotent.
3236+
3237+
31473238
Miscellaneous
31483239
-------------
31493240

docs/changelog.rst

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,24 @@ case, both modules must use the same nanobind ABI version, or they will be
1515
isolated from each other. Releases that don't explicitly mention an ABI version
1616
below inherit that of the preceding release.
1717

18+
Version TBD (unreleased)
19+
------------------------
20+
21+
.. TODO: update the pybind11 version number below before releasing
22+
23+
- nanobind has adopted the new `pymetabind
24+
<https://github.com/hudson-trading/pymetabind>`__ standard for interoperating
25+
with other Python binding libraries (including other ABI versions of
26+
nanobind). When the interoperability feature is activated, which in most cases
27+
is as simple as writing :cpp:func:`nb::interoperate_by_default()
28+
<interoperate_by_default>`, a function or method that is bound using nanobind
29+
can accept and return values of types that were bound using other binding
30+
libraries that support pymetabind, notably including pybind11 versions !TBD!
31+
and later. This feature is likely to be of **great utility** to anyone who
32+
is working on porting large or interconnected extension modules from pybind11
33+
to nanobind. See the extensive :ref:`interoperability documentation <interop>`
34+
for more details.
35+
1836
Version 2.8.0 (July 16, 2025)
1937
-----------------------------
2038

@@ -295,7 +313,7 @@ Version 2.3.0
295313

296314
There is no version 2.3.0 due to a deployment mishap.
297315

298-
- Added casters for `Eigen::Map<Eigen::SparseMatrix<...>` types from the `Eigen library
316+
- Added casters for ``Eigen::Map<Eigen::SparseMatrix<...>`` types from the `Eigen library
299317
<https://eigen.tuxfamily.org/index.php?title=Main_Page>`__. (PR `#782
300318
<https://github.com/wjakob/nanobind/pull/782>`_).
301319

docs/faq.rst

Lines changed: 71 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -233,33 +233,35 @@ will:
233233

234234
.. _type-visibility:
235235

236-
How can I avoid conflicts with other projects using nanobind?
237-
-------------------------------------------------------------
238-
239-
Suppose that a type binding in your project conflicts with another extension, for
240-
example because both expose a common type (e.g., ``std::latch``). nanobind will
241-
warn whenever it detects such a conflict:
236+
How can I control whether two extension modules see each other's types?
237+
-----------------------------------------------------------------------
238+
239+
nanobind creates a variety of internal data structures to support the bindings
240+
you ask it to make. These are not isolated to a single extension module,
241+
because it's useful for large binding projects to be able to split related
242+
bindings into multiple extensions without losing their ability to work with
243+
one another's types. Instead, extension modules are divided into nanobind
244+
*domains* based on two attributes: an automatically determined string (the
245+
nanobind "ABI tag") that captures compatibility-relevant aspects of their
246+
build environments and nanobind versions, and an ``NB_DOMAIN`` string that
247+
may be provided at build time (as shown below).
248+
If multiple extension modules share the same ABI tag and the same ``NB_DOMAIN``
249+
string, they will wind up in the same nanobind domain, which allows them to
250+
work together exactly as if they were one big extension.
251+
252+
Sometimes, this causes problems. For example, you might expose a binding
253+
for a commonly used type (such as ``std::latch``) that some other nanobind
254+
extension in your Python interpreter also happens to provide a binding for.
255+
nanobind will warn whenever it detects such a conflict:
242256

243257
.. code-block:: text
244258
245259
RuntimeWarning: nanobind: type 'latch' was already registered!
246260
247261
In the worst case, this could actually break both packages (especially if the
248-
bindings of the two packages expose an inconsistent/incompatible API).
249-
250-
The higher-level issue here is that nanobind will by default try to make type
251-
bindings visible across extensions because this is helpful to partition large
252-
binding projects into smaller parts. Such information exchange requires that
253-
the extensions:
254-
255-
- use the same nanobind *ABI version* (see the :ref:`Changelog <changelog>` for details).
256-
- use the same compiler (extensions built with GCC and Clang are isolated from each other).
257-
- use ABI-compatible versions of the C++ library.
258-
- use the stable ABI interface consistently (stable and unstable builds are isolated from each other).
259-
- use debug/release mode consistently (debug and release builds are isolated from each other).
260-
261-
In addition, nanobind provides a feature to intentionally scope extensions to a
262-
named domain to avoid conflicts with other extensions. To do so, specify the
262+
bindings of the two packages expose an inconsistent/incompatible API). So it's
263+
useful to be able to enforce a boundary between extensions sometimes, even if
264+
they would otherwise be ABI-compatible. To do so, you can specify the
263265
``NB_DOMAIN`` parameter in CMake:
264266

265267
.. code-block:: cmake
@@ -268,8 +270,54 @@ named domain to avoid conflicts with other extensions. To do so, specify the
268270
NB_DOMAIN my_project
269271
my_ext.cpp)
270272
271-
In this case, inter-extension type visibility is furthermore restricted to
272-
extensions in the ``"my_project"`` domain.
273+
Two extensions can only be in the same nanobind domain if either they both
274+
specify the same value for that parameter (``"my_project"`` in this case) or
275+
neither one specifies the parameter.
276+
277+
As mentioned above, two extensions can also only be in the same nanobind domain
278+
if they share the same ABI tag. This is determined in two parts, as follows:
279+
280+
- They must use compatible *platform ABI*, so that (for example) a
281+
``std::vector<int>`` created in one can be safely used in the other.
282+
That means:
283+
284+
- They must use the same C++ standard library (MSVC, libc++, or libstdc++),
285+
and the same ABI version of it. For example, extensions that use libstdc++
286+
must match in terms of whether they use the pre- or post-C++11 ABI, and
287+
extensions that use libc++ must use the same `libc++ ABI version
288+
<https://libcxx.llvm.org/DesignDocs/ABIVersioning.html>`__.
289+
290+
- On Windows, they must use the same compiler (MSVC, mingw, or cygwin).
291+
292+
- If compiled using MSVC, they must use the same major version of the
293+
compiler, the same multi-threading style (dynamic ``/MD``,
294+
static ``/MT``, or single-threaded), and they must match in terms of
295+
whether or not they are built in debugging mode.
296+
297+
- They must use compatible *nanobind ABI*, so that the nanobind internal data
298+
structures created in one can be safely used in the other. That means:
299+
300+
- They must use the same nanobind *ABI version*; see the
301+
:ref:`Changelog <changelog>` for details.
302+
303+
- They must be consistent in their use of Python's stable ABI: either
304+
both built against the stable ABI (cmake ``STABLE_ABI`` flag) or both not.
305+
306+
- They must be consistent in their use of free-threading: either both
307+
built with free-threading support (cmake ``FREE_THREADED`` flag) or both not.
308+
309+
- They must either both use released versions of nanobind or both be built
310+
from Git development snapshots, rather than a mix of the two.
311+
312+
If you want to share some types between two extensions that have the same
313+
platform ABI (the first category in the above list), but are in different
314+
nanobind domains due to using different nanobind ABI or different specified
315+
``NB_DOMAIN`` strings, all is not lost! The :ref:`interoperability support
316+
<interop>` between nanobind and other binding libraries also provides
317+
interoperability between different nanobind domains, as long as the platform
318+
ABI matches. It must be specifically enabled, and there are a few things it
319+
can't do, but for most purposes it's hard to tell the difference from operating
320+
within the same domain. See the linked documentation for more details.
273321

274322
Can I use nanobind without RTTI or C++ exceptions?
275323
--------------------------------------------------

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ The nanobind logo was designed by `AndoTwin Studio
128128
packaging
129129
typing
130130
utilities
131+
interop
131132

132133
.. toctree::
133134
:caption: Advanced

docs/refleaks.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ easily disable them by calling :cpp:func:`nb::set_leak_warnings()
5858
}
5959
6060
Note that is a *global flag* shared by all nanobind extension libraries in the
61-
same ABI domain. When changing global flags, please isolate your extension from
62-
others by passing the ``NB_DOMAIN`` parameter to the
61+
same `nanobind domain <type-visibility>`. When changing global flags, please
62+
isolate your extension from others by passing the ``NB_DOMAIN`` parameter to the
6363
:cmake:command:`nanobind_add_module()` CMake command:
6464

6565
.. code-block:: cmake

include/nanobind/nb_cast.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,15 +189,16 @@ struct type_caster<T, enable_if_t<std::is_arithmetic_v<T> && !is_std_char_v<T>>>
189189

190190
template <typename T>
191191
struct type_caster<T, enable_if_t<std::is_enum_v<T>>> {
192-
NB_INLINE bool from_python(handle src, uint8_t flags, cleanup_list *) noexcept {
192+
NB_INLINE bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
193193
int64_t result;
194-
bool rv = enum_from_python(&typeid(T), src.ptr(), &result, flags);
194+
bool rv = enum_from_python(&typeid(T), src.ptr(), &result, sizeof(T),
195+
flags, cleanup);
195196
value = (T) result;
196197
return rv;
197198
}
198199

199200
NB_INLINE static handle from_cpp(T src, rv_policy, cleanup_list *) noexcept {
200-
return enum_from_cpp(&typeid(T), (int64_t) src);
201+
return enum_from_cpp(&typeid(T), (int64_t) src, sizeof(T));
201202
}
202203

203204
NB_TYPE_CASTER(T, const_name<T>())
@@ -504,6 +505,8 @@ template <typename Type_> struct type_caster_base : type_caster_base_tag {
504505
} else {
505506
const std::type_info *type_p =
506507
(!has_type_hook && ptr) ? &typeid(*ptr) : nullptr;
508+
if (type_p && (void *) ptr != dynamic_cast<void *>(ptr))
509+
type_p = nullptr; // don't try to downcast from a non-primary base
507510
return nb_type_put_p(type, type_p, ptr, policy, cleanup);
508511
}
509512
}

include/nanobind/nb_class.h

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ struct enum_tbl_t {
106106
/// Information about a type that persists throughout its lifetime
107107
struct type_data {
108108
uint32_t size;
109-
uint32_t align : 8;
110-
uint32_t flags : 24;
109+
uint32_t align : 8; // always zero for an enum
110+
uint32_t flags : 24; // type_flags or enum_flags
111111
const char *name;
112112
const std::type_info *type;
113113
PyTypeObject *type_py;
@@ -212,6 +212,7 @@ struct enum_init_data {
212212
PyObject *scope;
213213
const char *name;
214214
const char *docstr;
215+
uint32_t size;
215216
uint32_t flags;
216217
};
217218

@@ -333,15 +334,16 @@ inline void *type_get_slot(handle h, int slot_id) {
333334
}
334335

335336
// nanobind interoperability with other binding frameworks
336-
inline void set_foreign_type_defaults(bool export_all, bool import_all) {
337-
detail::nb_type_set_foreign_defaults(export_all, import_all);
337+
inline void interoperate_by_default(bool export_all = true,
338+
bool import_all = true) {
339+
detail::nb_type_set_interop_defaults(export_all, import_all);
338340
}
339341
template <class T = void>
340-
inline void import_foreign_type(handle type) {
342+
inline void import_for_interop(handle type) {
341343
detail::nb_type_import(type.ptr(),
342344
std::is_void_v<T> ? nullptr : &typeid(T));
343345
}
344-
inline void export_type_to_foreign(handle type) {
346+
inline void export_for_interop(handle type) {
345347
detail::nb_type_export(type.ptr());
346348
}
347349

@@ -787,6 +789,7 @@ template <typename T> class enum_ : public object {
787789
NB_INLINE enum_(handle scope, const char *name, const Extra &... extra) {
788790
detail::enum_init_data ed { };
789791
ed.type = &typeid(T);
792+
ed.size = sizeof(T);
790793
ed.scope = scope.ptr();
791794
ed.name = name;
792795
ed.flags = std::is_signed_v<Underlying>

0 commit comments

Comments
 (0)