.. cpp:namespace:: nanobind
If importing the module fails as shown below, you have not specified a matching module name in :cmake:command:`nanobind_add_module()` and :c:macro:`NB_MODULE() <NB_MODULE>`.
>>> import my_ext
ImportError: dynamic module does not define module export function (PyInit_my_ext)
If importing the module fails as shown below, the extension cannot find the
nanobind
shared library component.
>>> import my_ext
ImportError: dlopen(my_ext.cpython-311-darwin.so, 0x0002):
Library not loaded: '@rpath/libnanobind.dylib'
This is really more of a general C++/CMake/build system issue than one of nanobind specifically. There are two solutions:
Build the library component statically by specifying the
NB_STATIC
flag in :cmake:command:`nanobind_add_module()` (this is the default starting with nanobind 0.2.0).Ensure that the various shared libraries are installed in the right destination, and that their rpath is set so that they can find each other.
You can control the build output directory of the shared library component using the following CMake command:
set_target_properties(nanobind PROPERTIES LIBRARY_OUTPUT_DIRECTORY <path> LIBRARY_OUTPUT_DIRECTORY_RELEASE <path> LIBRARY_OUTPUT_DIRECTORY_DEBUG <path> LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO <path> LIBRARY_OUTPUT_DIRECTORY_MINSIZEREL <path> )
Depending on the flags provided to :cmake:command:`nanobind_add_module()`, the shared library component may have a different name following the pattern
nanobind[-abi3][-lto]
.The following CMake commands may be useful to adjust the build and install rpath of the extension:
set_property(TARGET my_ext APPEND PROPERTY BUILD_RPATH "$<TARGET_FILE_DIR:nanobind>") set_property(TARGET my_ext APPEND PROPERTY INSTALL_RPATH ".. ?? ..")
Functions like the following example can be exposed in Python, but they won't propagate updates to mutable reference arguments.
void increment(int &i) {
i++;
}
This isn't specific to builtin types but also applies to STL collections and other types when they are handled using :ref:`type casters <type_casters>`. Please read the full section on :ref:`information exchange between C++ and Python <exchange>` to understand the issue and alternatives.
Please see the dedicated documentation section on :ref:`reference leaks <refleaks>`.
If your compiler generates an error of the following sort, you are mixing type casters and bindings in a way that has them competing for the same types:
nanobind/include/nanobind/nb_class.h:207:40: error: static assertion failed: ↵
Attempted to create a constructor for a type that won't be handled by the nanobind's ↵
class type caster. Is it possible that you forgot to add NB_MAKE_OPAQUE() somewhere?
For example, the following won't work:
#include <nanobind/stl/vector.h>
#include <nanobind/stl/bind_vector.h>
namespace nb = nanobind;
NB_MODULE(my_ext, m) {
// The following line cannot be compiled
nb::bind_vector<std::vector<int>>(m, "VectorInt");
// This doesn't work either
nb::class_<std::vector<int>>(m, "VectorInt");
}
This is not specific to STL vectors and will happen whenever casters and bindings target overlapping types.
:ref:`Type casters <type_casters>` employ a pattern matching technique known as
partial template specialization. For example,
nanobind/stl/vector.h
installs a pattern that detects any use of
std::vector<T, Allocator>
, which overlaps with the above binding of a specific
vector type.
The deeper reason for this conflict is that type casters enable a compile-time transformation of nanobind code, which can conflict with binding declarations that are a runtime construct.
To fix the conflict in this example, add the line :c:macro:`NB_MAKE_OPAQUE(T)
<NB_MAKE_OPAQUE>`, which adds another partial template specialization pattern
for T
that says: "ignore T
and don't use a type caster to handle it".
NB_MAKE_OPAQUE(std::vector<int>);
Warning
If your extension consists of multiple source code files that involve overlapping use of type casters and bindings, you are treading on thin ice. It is easy to violate the One Definition Rule (ODR) [details] in such a case, which may lead to undefined behavior (miscompilations, etc.).
Here is a hypothetical example of an ODR violation: an extension
contains two source code files: src_1.cpp
and src_2.cpp
.
src_1.cpp
binds a function that returns anstd::vector<int>
using a :ref:`type caster <type_casters>` (nanobind/stl/vector.h
).src_2.cpp
binds a function that returns anstd::vector<int>
using a :ref:`binding <bindings>` (nanobind/stl/bind_vector.h
), and it also installs the needed type binding.
The problem is that a partially specialized class in the nanobind
implementation namespace (specifically,
nanobind::detail::type_caster<std::vector<int>>
) now resolves to two
different implementations in the two compilation units. It is unclear how
such a conflict should be resolved at the linking stage, and you should
consider code using such constructions broken.
To avoid this issue altogether, we recommend that you create a single
include file (e.g., binding_core.h
) containing all of the nanobind
include files (binding, type casters), your own custom type casters (if
present), and :c:macro:`NB_MAKE_OPAQUE(T) <NB_MAKE_OPAQUE>` declarations.
Include this header consistently in all binding compilation units. The
construction shown in the example (mixing type casters and bindings for the
same type) is not allowed, and cannot occur when following the
recommendation.
This is a limitation of nanobind, which casts away const
in function
arguments and return values. This is in line with the Python language, which
has no concept of const values. Additional care is therefore needed to avoid
bugs that would be caught by the type checker in a traditional C++ program.
Large binding projects should be partitioned into multiple files, as shown in the following example:
void init_ex1(nb::module_ &);
void init_ex2(nb::module_ &);
/* ... */
NB_MODULE(my_ext, m) {
init_ex1(m);
init_ex2(m);
/* ... */
}
void init_ex1(nb::module_ &m) {
m.def("add", [](int a, int b) { return a + b; });
}
void init_ex2(nb::module_ &m) {
m.def("sub", [](int a, int b) { return a - b; });
}
As shown above, the various init_ex
functions should be contained in
separate files that can be compiled independently from one another, and then
linked together into the same final shared object. Following this approach
will:
- reduce memory requirements per compilation unit.
- enable parallel builds (if desired).
- allow for faster incremental builds. For instance, when a single class definition is changed, only a subset of the binding code will generally need to be recompiled.
Suppose that a type binding in your project conflicts with another extension, for
example because both expose a common type (e.g., std::latch
). nanobind will
warn whenever it detects such a conflict:
RuntimeWarning: nanobind: type 'latch' was already registered!
In the worst case, this could actually break both packages (especially if the bindings of the two packages expose an inconsistent/incompatible API).
The higher-level issue here is that nanobind will by default try to make type bindings visible across extensions because this is helpful to partition large binding projects into smaller parts. Such information exchange requires that the extensions:
- use the same nanobind ABI version (see the :ref:`Changelog <changelog>` for details).
- use the same compiler (extensions built with GCC and Clang are isolated from each other).
- use ABI-compatible versions of the C++ library.
- use the stable ABI interface consistently (stable and unstable builds are isolated from each other).
- use debug/release mode consistently (debug and release builds are isolated from each other).
In addition, nanobind provides a feature to intentionally scope extensions to a
named domain to avoid conflicts with other extensions. To do so, specify the
NB_DOMAIN
parameter in CMake:
nanobind_add_module(my_ext
NB_DOMAIN my_project
my_ext.cpp)
In this case, inter-extension type visibility is furthermore restricted to
extensions in the "my_project"
domain.
Certain environments (e.g., Google-internal development, embedded devices, etc.)
require compilation without C++ runtime type information (-fno-rtti
) and
exceptions (-fno-exceptions
).
nanobind requires both of these features and cannot be used when they are not available. RTTI provides the central index to look up types of bindings. Exceptions are needed because Python relies on exceptions that must be converted into something equivalent on the C++ side. PRs that refactor nanobind to work without RTTI or exceptions will not be accepted.
For Googlers: there is already an exemption from the internal rules that specifically permits the use of RTTI/exceptions when a project relies on pybind11. Likely, this exemption could be extended to include nanobind as well.
Stable ABI extensions are convenient because they can be reused across Python
versions, but this unfortunately only works on Python 3.12 and newer. Nanobind
crucially depends on several features that were added
in version 3.12 (specifically, PyType_FromMetaclass()
and limited API
bindings of the vector call protocol).
nanobind regularly receives requests from users who run it through Clang-Tidy,
or who compile with increased warnings levels, like -Wpedantic
,
-Wcast-qual
, -Wsign-conversion
, etc. (i.e., beyond the increased
-Wall
, -Wextra
and /W4
warning levels that are already enabled)
Their next step is to open a big pull request needed to silence all of the resulting messages.
My policy on this is as follows: I am always happy to fix issues in the codebase. However, many of the resulting change requests are in the "ritual purification" category: things that cause churn, decrease readability, and which don't fix actual problems. It's a never-ending cycle because each new revision of such tooling adds further warnings and purification rites.
So just to have a clear policy: I do not wish to pepper this codebase with
const_cast
and #pragmas
or pragma-like comments to avoid warnings in
various kinds of external tooling just so those users can have a "silent"
build. I don't think it is reasonable for them to impose their own style on
this project.
As a workaround it is likely possible to restrict the scope of style checks to particular C++ namespaces or source code locations.
A difficult aspect of C++ software development is the sheer number of competing build systems, including
The author of this project has some familiarity with CMake but lacks expertise with this large space of alternative tools. Maintaining and shipping support for other build systems is therefore considered beyond the scope of this nano project (see also the :ref:`why? <why>` part of the documentation that explains the rationale for being somewhat restrictive towards external contributions).
If you wish to create and maintain an alternative interface to nanobind, then
my request would be that you create and maintain separate repository (see,
e.g., pybind11_bazel as an
example how how this was handled in the case of pybind11). Please carefully
review the file nanobind-config.cmake.
Besides getting things to compile, it specifies a number of platform-dependent
compiler and linker options that are needed to produce optimal (small and
efficient) binaries. Nanobind uses a complicated and non-standard
set of linker parameters on macOS, which is the result of a lengthy
investigation.
Other parameters like linker-level dead code elimination and size-based
optimization were similarly added following careful analysis. The CMake build
system provides the ability to compile libnanobind
into either a shared or
a static library, to optionally target the stable ABI, and to isolate it from
other extensions via the NB_DOMAIN
parameter. All of these are features
that would be nice to retain in an alternative build system. If you've made a
build system compatible with another tool that is sufficiently
feature-complete, then please file an issue and I am happy to reference it in
the documentation.
litgen is an automatic Python bindings generator compatible with both pybind11 and nanobind, designed to create documented and easily discoverable bindings. It reproduces header documentation directly in the bindings, making the generated API intuitive and well-documented for Python users. Powered by srcML (srcml.org), a high-performance, multi-language parsing tool, litgen takes a developer-centric approach. The C++ API to be exposed to Python must be C++14 compatible, although the implementation can leverage more modern C++ features.
Please use the following BibTeX template to cite nanobind in scientific discourse:
@misc{nanobind,
author = {Wenzel Jakob},
year = {2022},
note = {https://github.com/wjakob/nanobind},
title = {nanobind: tiny and efficient C++/Python bindings}
}