This is an automated email from the ASF dual-hosted git repository.
junrushao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-ffi.git
The following commit(s) were added to refs/heads/main by this push:
new 34ad335 doc: Any and AnyView (#382)
34ad335 is described below
commit 34ad335202b27cc802441675571e07ec161556b2
Author: Junru Shao <[email protected]>
AuthorDate: Thu Jan 8 09:33:21 2026 -0800
doc: Any and AnyView (#382)
---
docs/concepts/abi_overview.md | 5 +
docs/concepts/any.rst | 436 ++++++++++++++++++++++++++++++++++
docs/get_started/stable_c_abi.rst | 2 +-
docs/guides/cpp_lang_guide.md | 5 +
docs/index.rst | 1 +
python/tvm_ffi/dataclasses/c_class.py | 2 +-
python/tvm_ffi/dataclasses/field.py | 2 +-
7 files changed, 450 insertions(+), 3 deletions(-)
diff --git a/docs/concepts/abi_overview.md b/docs/concepts/abi_overview.md
index 7762ede..573f7e4 100644
--- a/docs/concepts/abi_overview.md
+++ b/docs/concepts/abi_overview.md
@@ -266,6 +266,11 @@ values are unique by appending namespace prefix to the key.
## AnyView and Managed Any
+```{seealso}
+For a comprehensive tutorial on Any including ownership semantics, extraction
methods,
+and common patterns, see {doc}`any`.
+```
+
An `TVMFFIAny` can either be treated as a strongly managed value
(corresponding to `ffi::Any` in C++),
or an unmanaged value (corresponding to `ffi::AnyView` in C++).
diff --git a/docs/concepts/any.rst b/docs/concepts/any.rst
new file mode 100644
index 0000000..b6e38f1
--- /dev/null
+++ b/docs/concepts/any.rst
@@ -0,0 +1,436 @@
+.. Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+.. http://www.apache.org/licenses/LICENSE-2.0
+
+.. Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+Any and AnyView
+===============
+
+TVM-FFI has :cpp:class:`tvm::ffi::Any` and :cpp:class:`tvm::ffi::AnyView`,
+type-erased containers that hold any supported value and transport it across
+C, C++, Python, and Rust boundaries through a stable ABI.
+
+Similar to ``std::any``, :cpp:class:`~tvm::ffi::Any` is a tagged union that
stores
+values of a wide variety of types, including primitives, objects, and strings.
+Unlike ``std::any``, it is designed for zero-copy inter-language exchange
without RTTI,
+featuring a fixed 16-byte layout with built-in reference counting and
ownership semantics.
+
+This tutorial covers everything you need to know about
:cpp:class:`~tvm::ffi::Any` and :cpp:class:`~tvm::ffi::AnyView`:
+common usage patterns, ownership semantics, and memory layout.
+
+
+Common Usage
+------------
+
+Function Signatures
+~~~~~~~~~~~~~~~~~~~
+
+Use :cpp:class:`~tvm::ffi::AnyView` for function parameters
+to avoid reference count overhead and unnecessary copies.
+Use :cpp:class:`~tvm::ffi::Any` for return values to transfer ownership to the
caller.
+
+.. code-block:: cpp
+
+ ffi::Any func_cpp_signature(ffi::AnyView arg0, ffi::AnyView arg1) {
+ ffi::Any result = arg0.cast<int>() + arg1.cast<int>();
+ return result;
+ }
+
+ // Variant: variadic function
+ void func_cpp_variadic(PackedArgs args, Any* ret) {
+ int32_t num_args = args.size();
+ int x0 = args[0].cast<int>();
+ int x1 = args[1].cast<int>();
+ int y = x0 + x1;
+ *ret = y;
+ }
+
+ // Variant: variadic function with C ABI signature
+ int func_c_abi_variadic(void*, const TVMFFIAny* args, int32_t num_args,
TVMFFIAny* ret) {
+ TVM_FFI_SAFE_CALL_BEGIN();
+ int x0 = reinterpret_cast<const AnyView*>(args)[0].cast<int>();
+ int x1 = reinterpret_cast<const AnyView*>(args)[1].cast<int>();
+ int y = x0 + x1;
+ reinterpret_cast<Any*>(ret)[0] = y;
+ TVM_FFI_SAFE_CALL_END();
+ }
+
+
+Container Storage
+~~~~~~~~~~~~~~~~~
+
+:cpp:class:`~tvm::ffi::Any` can be stored in containers like
:cpp:class:`~tvm::ffi::Map` and :cpp:class:`~tvm::ffi::Array`:
+
+.. code-block:: cpp
+
+ ffi::Map<ffi::String, ffi::Any> config;
+ config.Set("learning_rate", 0.001);
+ config.Set("batch_size", 32);
+ config.Set("device", DLDevice{kDLCUDA, 0});
+
+Extracting Values
+~~~~~~~~~~~~~~~~~
+
+Three methods extract values from :cpp:class:`~tvm::ffi::Any`
+and :cpp:class:`~tvm::ffi::AnyView`, each with different levels of strictness:
+
+.. list-table::
+ :header-rows: 1
+ :widths: 20 45 30
+
+ * - Method
+ - Behavior
+ - Use When
+ * - :cpp:func:`cast\<T\>() <tvm::ffi::Any::cast>`
+ - Returns ``T`` or throws :cpp:class:`tvm::ffi::Error`
+ - When you know the expected type and want an exception on mismatch
+ * - :cpp:func:`try_cast\<T\>() <tvm::ffi::Any::try_cast>`
+ - Returns ``std::optional<T>``
+ - When you want graceful failure and allow type conversions (e.g., int to
double)
+ * - :cpp:func:`as\<T\>() <tvm::ffi::Any::as>`
+ - Returns ``std::optional<T>`` or ``const T*`` (for TVM-FFI object types)
+ - When you need an exact type match with no conversions
+
+.. dropdown:: Example of :cpp:func:`cast\<T\>() <tvm::ffi::Any::cast>`
+
+ :cpp:func:`cast\<T\>() <tvm::ffi::Any::cast>` is the workhorse. It returns
the value or throws:
+
+ .. code-block:: cpp
+
+ ffi::Any value = 42;
+ int x = value.cast<int>(); // OK: 42
+ double y = value.cast<double>(); // OK: 42.0 (int → double)
+
+ try {
+ ffi::String s = value.cast<ffi::String>(); // Throws TypeError
+ } catch (const ffi::Error& e) {
+ // "Cannot convert from type `int` to `ffi.Str`"
+ }
+
+.. dropdown:: Example of :cpp:func:`try_cast\<T\>() <tvm::ffi::Any::try_cast>`
+
+ :cpp:func:`try_cast\<T\>() <tvm::ffi::Any::try_cast>` allows type coercion:
+
+ .. code-block:: cpp
+
+ ffi::Any value = 42;
+
+ std::optional<double> opt_float = value.try_cast<double>();
+ // opt_float.has_value() == true, *opt_float == 42.0
+
+ std::optional<bool> opt_bool = value.try_cast<bool>();
+ // opt_bool.has_value() == true, *opt_bool == true
+
+
+.. dropdown:: Example of :cpp:func:`as\<T\>() <tvm::ffi::Any::as>`
+
+ :cpp:func:`as\<T\>() <tvm::ffi::Any::as>` is strict - it succeeds only if
the stored type matches exactly:
+
+ .. code-block:: cpp
+
+ ffi::Any value = 42;
+
+ std::optional<int64_t> opt_int = value.as<int64_t>();
+ // opt_int.has_value() == true
+
+ std::optional<double> opt_float = value.as<double>();
+ // opt_float.has_value() == false (int stored, not float)
+
+ ffi::Any str_value = ffi::String("hello, world!");
+ if (const ffi::Object* obj = str_value.as<ffi::Object>()) {
+ // Use obj without copying
+ }
+
+
+Nullability Checks
+~~~~~~~~~~~~~~~~~~
+
+Compare with ``nullptr`` to check for ``None``:
+
+.. code-block:: cpp
+
+ ffi::Any value = std::nullopt;
+ if (value == nullptr) {
+ // Handle None case
+ } else {
+ // Process value
+ }
+
+
+Ownership
+---------
+
+The core distinction between :cpp:class:`tvm::ffi::Any` and
+:cpp:class:`tvm::ffi::AnyView` is **ownership**:
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 35 40
+
+ * - Aspect
+ - :cpp:class:`~tvm::ffi::AnyView`
+ - :cpp:class:`~tvm::ffi::Any`
+ * - Ownership
+ - Non-owning (like ``std::string_view``)
+ - Owning (like ``std::string``)
+ * - Reference counting
+ - No reference count changes on copy
+ - Increments reference count on copy; decrements on destroy
+ * - Lifetime
+ - Valid only while source lives
+ - Extends object lifetime
+ * - Primary use
+ - Function inputs
+ - Return values, storage
+
+Code Examples
+~~~~~~~~~~~~~~
+
+:cpp:class:`~tvm::ffi::AnyView` is a lightweight, non-owning view. Copying it
simply
+copies 16 bytes with no reference count updates, making it ideal for passing
arguments without overhead:
+
+.. code-block:: cpp
+
+ void process(ffi::AnyView value) {}
+
+:cpp:class:`~tvm::ffi::Any` is an owning container. Copying an
:cpp:class:`~tvm::ffi::Any` that holds an object
+increments the reference count; destroying it decrements the count:
+
+.. code-block:: cpp
+
+ ffi::Any create_value() {
+ ffi::Any result;
+ {
+ ffi::String str = "hello"; // refcount = 1 (str created)
+ result = str; // refcount 1 -> 2 (result owns str)
+ } // refcount 2 -> 1 (str is destroyed)
+ return result; // refcount = 1 (result returns to caller)
+ }
+
+
+ABI Boundary
+~~~~~~~~~~~~
+
+TVM-FFI's function calling convention follows two simple rules:
+
+- **Inputs are non-owning**: Arguments are passed as
:cpp:class:`~tvm::ffi::AnyView`. The caller
+ retains ownership, and the callee borrows them for the duration of the call.
+- **Outputs are owning**: Return values are passed as
:cpp:class:`~tvm::ffi::Any`. Ownership transfers
+ to the caller, who becomes responsible for managing the value's lifetime.
+
+.. code-block:: cpp
+
+ // TVM-FFI C ABI
+ int32_t tvm_ffi_c_abi(
+ void* handle,
+ const AnyView* args, // (Non-owning) args: AnyView[num_args]
+ int32_t num_args,
+ Any* result, // (Owning) result: Any (caller takes ownership)
+ );
+
+Destruction Semantics in C
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In C, which lacks RAII, you must manually destroy :cpp:class:`~tvm::ffi::Any`
objects
+by calling :cpp:func:`TVMFFIObjectDecRef` for heap-allocated objects.
+
+.. code-block:: cpp
+
+ void destroy_any(TVMFFIAny* any) {
+ if (any->type_index >= kTVMFFIStaticObjectBegin) {
+ // Decrement the reference count of the heap-allocated object
+ TVMFFIObjectDecRef(any->v_obj);
+ }
+ *any = (TVMFFIAny){0};
+ }
+
+In contrast, destroying an :cpp:class:`~tvm::ffi::AnyView` is effectively a
no-op - just clear its contents.
+
+.. code-block:: cpp
+
+ void destroy_any_view(TVMFFIAny* any_view) {
+ *any_view = (TVMFFIAny){0};
+ }
+
+
+Layout
+------
+
+Tagged Union
+~~~~~~~~~~~~
+
+At C ABI level, every value lives in a :cpp:class:`TVMFFIAny`:
+
+.. code-block:: cpp
+
+ typedef struct TVMFFIAny {
+ int32_t type_index; // Bytes 0-3: identifies the stored type
+ union {
+ uint32_t zero_padding; // Bytes 4-7: must be zero (or small_str_len)
+ uint32_t small_str_len;
+ };
+ union { // Bytes 8-15: the actual value
+ int64_t v_int64;
+ double v_float64;
+ void* v_ptr;
+ DLDataType v_dtype;
+ DLDevice v_device;
+ TVMFFIObject* v_obj;
+ // ... other union members
+ };
+ } TVMFFIAny;
+
+
+.. tip::
+
+ Think of :cpp:class:`TVMFFIAny` as the "layout format", and
:cpp:class:`~tvm::ffi::Any`/:cpp:class:`~tvm::ffi::AnyView`
+ as a thin "application layer" that adds type safety, RAII, and ergonomic
APIs, which has no change to the layout.
+
+.. figure::
https://raw.githubusercontent.com/tlc-pack/web-data/main/images/tvm-ffi/stable-c-abi-layout-any.svg
+ :alt: Layout of the 128-bit Any tagged union
+ :align: center
+
+ Figure 1. Layout of the :cpp:class:`TVMFFIAny` tagged union in C ABI.
:cpp:class:`~tvm::ffi::Any`/:cpp:class:`~tvm::ffi::AnyView`
+ shares the same layout as :cpp:class:`TVMFFIAny`, but adds extra C++ APIs
on top of it for type safety, RAII, and ergonomics.
+
+It is effectively a layout-stable 16-byte tagged union.
+
+* The first 4 bytes (:cpp:member:`TVMFFIAny::type_index`) serve as a tag
identifying the stored type.
+* The last 8 bytes hold the actual value - either stored inline for atomic
types (e.g., ``int64_t``, ``float64``, ``void*``) or as a pointer to a
heap-allocated object.
+
+Atomic Types
+~~~~~~~~~~~~
+
+Primitive values - integers, floats, booleans, devices, and raw pointers - are
stored
+directly in the 8-byte payload with no heap allocation and no reference
counting.
+
+.. list-table:: Figure 2. Common atomic types stored directly in
:cpp:class:`TVMFFIAny`
+ :header-rows: 1
+ :name: atomic-types-table
+ :widths: 40 40 30
+
+ * - Type
+ - type_index
+ - Payload Field
+ * - ``None`` / ``nullptr``
+ - :cpp:enumerator:`kTVMFFINone <TVMFFITypeIndex::kTVMFFINone>` = 0
+ - :cpp:member:`~TVMFFIAny::v_int64` (must be 0)
+ * - ``int64_t``
+ - :cpp:enumerator:`kTVMFFIInt <TVMFFITypeIndex::kTVMFFIInt>` = 1
+ - :cpp:member:`~TVMFFIAny::v_int64`
+ * - ``bool``
+ - :cpp:enumerator:`kTVMFFIBool <TVMFFITypeIndex::kTVMFFIBool>` = 2
+ - :cpp:member:`~TVMFFIAny::v_int64` (0 or 1)
+ * - ``float64_t``
+ - :cpp:enumerator:`kTVMFFIFloat <TVMFFITypeIndex::kTVMFFIFloat>` = 3
+ - :cpp:member:`~TVMFFIAny::v_float64`
+ * - ``void*`` (opaque pointer)
+ - :cpp:enumerator:`kTVMFFIOpaquePtr <TVMFFITypeIndex::kTVMFFIOpaquePtr>`
= 4
+ - :cpp:member:`~TVMFFIAny::v_ptr`
+ * - :c:struct:`DLDataType <DLDataType>`
+ - :cpp:enumerator:`kTVMFFIDataType <TVMFFITypeIndex::kTVMFFIDataType>` = 5
+ - :cpp:member:`~TVMFFIAny::v_dtype`
+ * - :c:struct:`DLDevice <DLDevice>`
+ - :cpp:enumerator:`kTVMFFIDevice <TVMFFITypeIndex::kTVMFFIDevice>` = 6
+ - :cpp:member:`~TVMFFIAny::v_device`
+ * - :c:struct:`DLTensor* <DLTensor>`
+ - :cpp:enumerator:`kTVMFFIDLTensorPtr
<TVMFFITypeIndex::kTVMFFIDLTensorPtr>` = 7
+ - :cpp:member:`~TVMFFIAny::v_ptr`
+ * - ``const char*`` (raw string)
+ - :cpp:enumerator:`kTVMFFIRawStr <TVMFFITypeIndex::kTVMFFIRawStr>` = 8
+ - :cpp:member:`~TVMFFIAny::v_c_str`
+ * - :cpp:class:`TVMFFIByteArray* <TVMFFIByteArray>`
+ - :cpp:enumerator:`kTVMFFIByteArrayPtr
<TVMFFITypeIndex::kTVMFFIByteArrayPtr>` = 9
+ - :cpp:member:`~TVMFFIAny::v_ptr`
+
+:ref:`Figure 2 <atomic-types-table>` shows common atomic types stored in-place
inside the
+:cpp:class:`TVMFFIAny` payload.
+
+.. code-block:: cpp
+
+ AnyView int_val = 42; // v_int64 = 42
+ AnyView float_val = 3.14; // v_float64 = 3.14
+ AnyView bool_val = true; // v_int64 = 1
+ AnyView device = DLDevice{kDLCUDA, 0}; // v_device
+ DLTensor tensor;
+ AnyView view = &tensor; // v_ptr = &tensor
+
+Note that raw pointers like :c:struct:`DLTensor* <DLTensor>` and ``char*``
also fit here.
+These pointers carry no ownership, so the caller must ensure the pointed-to
data outlives
+the :cpp:class:`~tvm::ffi::AnyView` or :cpp:class:`~tvm::ffi::Any`.
+
+Heap-Allocated Objects
+~~~~~~~~~~~~~~~~~~~~~~
+
+.. list-table:: Figure 3. Common TVM-FFI object types stored as pointers in
:cpp:member:`TVMFFIAny::v_obj`.
+ :header-rows: 1
+ :widths: 40 40 30
+
+ * - Type
+ - type_index
+ - Payload Field
+ * - :cpp:class:`ErrorObj* <tvm::ffi::ErrorObj>`
+ - :cpp:enumerator:`kTVMFFIError <TVMFFITypeIndex::kTVMFFIError>` = 67
+ - :cpp:member:`~TVMFFIAny::v_obj`
+ * - :cpp:class:`FunctionObj* <tvm::ffi::FunctionObj>`
+ - :cpp:enumerator:`kTVMFFIFunction <TVMFFITypeIndex::kTVMFFIFunction>` =
68
+ - :cpp:member:`~TVMFFIAny::v_obj`
+ * - :cpp:class:`TensorObj* <tvm::ffi::TensorObj>`
+ - :cpp:enumerator:`kTVMFFITensor <TVMFFITypeIndex::kTVMFFITensor>` = 70
+ - :cpp:member:`~TVMFFIAny::v_obj`
+ * - :cpp:class:`ArrayObj* <tvm::ffi::ArrayObj>`
+ - :cpp:enumerator:`kTVMFFIArray <TVMFFITypeIndex::kTVMFFIArray>` = 71
+ - :cpp:member:`~TVMFFIAny::v_obj`
+ * - :cpp:class:`MapObj* <tvm::ffi::MapObj>`
+ - :cpp:enumerator:`kTVMFFIMap <TVMFFITypeIndex::kTVMFFIMap>` = 72
+ - :cpp:member:`~TVMFFIAny::v_obj`
+ * - :cpp:class:`ModuleObj* <tvm::ffi::ModuleObj>`
+ - :cpp:enumerator:`kTVMFFIModule <TVMFFITypeIndex::kTVMFFIModule>` = 73
+ - :cpp:member:`~TVMFFIAny::v_obj`
+
+
+Heap-allocated objects - :cpp:class:`~tvm::ffi::String`,
:cpp:class:`~tvm::ffi::Function`, :cpp:class:`~tvm::ffi::Tensor`,
:cpp:class:`~tvm::ffi::Array`, :cpp:class:`~tvm::ffi::Map`, and custom types -
are
+stored as pointers to reference-counted :cpp:class:`TVMFFIObject` headers:
+
+.. code-block:: cpp
+
+ ffi::String str = "hello world";
+ ffi::Any any_str = str; // v_obj points to StringObj
+
+ // Object layout in memory:
+ // [TVMFFIObject header (24 bytes)][object-specific data]
+
+
+Caveats
+-------
+
+Small String Optimization
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Strings and byte arrays receive special treatment: values of 7 bytes or fewer
are stored
+inline using **small string optimization**, avoiding heap allocation entirely:
+
+.. code-block:: cpp
+
+ ffi::Any small = "hello"; // kTVMFFISmallStr, in v_bytes
+ ffi::Any large = "this is a longer string"; // kTVMFFIStr, heap allocated
+
+
+Further Reading
+---------------
+
+- **C examples**: :doc:`../get_started/stable_c_abi` demonstrates working with
:cpp:class:`TVMFFIAny` directly in C
+- **Tensor conversions**: :doc:`tensor` covers how tensors flow through
:cpp:class:`~tvm::ffi::Any` and :cpp:class:`~tvm::ffi::AnyView`
+- **Function calling**: :doc:`../guides/cpp_lang_guide` explains how functions
use :cpp:class:`~tvm::ffi::Any` for arguments and returns
diff --git a/docs/get_started/stable_c_abi.rst
b/docs/get_started/stable_c_abi.rst
index abab5c2..b8d8195 100644
--- a/docs/get_started/stable_c_abi.rst
+++ b/docs/get_started/stable_c_abi.rst
@@ -123,7 +123,7 @@ Stability and Interoperability
**Stability.** The pure C layout and the calling convention are stable across
compiler versions and independent of host languages or frameworks.
-**Cross-language.** TVM-FFI implements this calling convention in multiple
languages (C, C++, Python, Rust, ...), enabling code written in one language—or
generated by a DSL targeting the ABI—to be called from another language.
+**Cross-language.** TVM-FFI implements this calling convention in multiple
languages (C, C++, Python, Rust, ...), enabling code written in one language -
or generated by a DSL targeting the ABI - to be called from another language.
**Cross-framework.** TVM-FFI uses standard data structures such as
:external+data-api:doc:`DLPack tensors <design_topics/data_interchange>` to
represent arrays, so compiled functions can be used from any array framework
that implements the DLPack protocol (NumPy, PyTorch, TensorFlow, CuPy, JAX, and
others).
diff --git a/docs/guides/cpp_lang_guide.md b/docs/guides/cpp_lang_guide.md
index e5f09b7..f8ab8e5 100644
--- a/docs/guides/cpp_lang_guide.md
+++ b/docs/guides/cpp_lang_guide.md
@@ -32,6 +32,11 @@ You can find runnable code of the examples under
tests/cpp/test_example.cc.
## Any and AnyView
+```{seealso}
+For a deep dive into Any including memory layout, ownership semantics, and the
type conversion
+machinery, see {doc}`../concepts/any`.
+```
+
`Any` and `AnyView` are the foundation of tvm-ffi, providing
ways to store values that are compatible with the ffi system.
The following example shows how we can interact with Any and AnyView.
diff --git a/docs/index.rst b/docs/index.rst
index f6eced7..dc92c98 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -59,6 +59,7 @@ Table of Contents
:caption: Concepts
concepts/abi_overview.md
+ concepts/any.rst
concepts/tensor.rst
.. toctree::
diff --git a/python/tvm_ffi/dataclasses/c_class.py
b/python/tvm_ffi/dataclasses/c_class.py
index 8c2706b..65d7c73 100644
--- a/python/tvm_ffi/dataclasses/c_class.py
+++ b/python/tvm_ffi/dataclasses/c_class.py
@@ -55,7 +55,7 @@ def c_class(
The intent is to offer a familiar dataclass authoring experience while
still
exposing the underlying C++ object. The ``type_key`` of the C++ class must
match the string passed to :func:`c_class`, and inheritance relationships
are
- preserved—subclasses registered in C++ can subclass the Python proxy
defined
+ preserved-subclasses registered in C++ can subclass the Python proxy
defined
for their parent.
Parameters
diff --git a/python/tvm_ffi/dataclasses/field.py
b/python/tvm_ffi/dataclasses/field.py
index 95618cd..d10612c 100644
--- a/python/tvm_ffi/dataclasses/field.py
+++ b/python/tvm_ffi/dataclasses/field.py
@@ -32,7 +32,7 @@ class Field:
the ``Field`` instances, records the ``default_factory`` and later replaces
the field with a property that forwards to the underlying C++ attribute.
- Users should not instantiate ``Field`` directly—use :func:`field` instead,
+ Users should not instantiate ``Field`` directly - use :func:`field`
instead,
which guarantees that ``name`` and ``default_factory`` are populated in a
way the decorator understands.
"""