Meta-Reference: Documentation Attributes#

Some APIs in this documentation contain non-standard language attributes on functions, parameters, types, and variables. These only appear in the documentation and are not part of the actual C API headers. They do not effect the actual compilation or behavior of the program, and are intended to act as common shorthands for behavioral guarantees of the annotated APIs.

Non-Standard Attributes#

[[transfer]]#

[[transfer]] (documentation attribute)#

Understanding the meaning of [[transfer]] is essential to understanding object lifetimes in amongoc. The [[transfer]] has two significant points:

  1. If a function parameter is annotated with [[transfer]], it means that passing a value to that function will invalidate the value in the caller’s scope.

  2. When an object is passed through a parameter that is marked with [[transfer]], it becomes the responsibility of the called function to manage the lifetime of the associated object. The function must eventually pass the object to another function that has an [[transfer]] attribute.[2]

According to point 1: If you pass an lvalue to an object \(V\) through a parameter marked [[transfer]], then the \(V\) object is now invalid. See: Dealing with Transferred Objects.

According to point 2: If you pass a function pointer to an API, and the function pointer type has [[transfer]] parameters, then any value passed in through that parameter now owns the respective object, and it must handle it properly:

// `call_through` transfers ownership of an object through `i`
extern int call_through(void (*callback)(int [[transfer]] i));

// For use with `call_through`
int my_callback(int i) {
  // We own the `i` object!
}

int my_code() {
  call_through(&my_callback);
}

Example: POSIX close#

A correct use of [[transfer]] would be on the close() POSIX API:

int close(int [[transfer]] filedes);

Because we are passing filedes by-value to close(), it is not possible for close() to actually modify our copy of the file descriptor. Nevertheless, close() will invalidate the file descriptor, even if our copy is completely unmodified:

int fd = get_fileno();  // [1]
close(fd);              // [2]
use_file(fd);           // [3]

Even though the fd value on [3] is bitwise-equal to the value it was assigned on [1], it is inherently erroneous: The call to close() on [2] has made the bit pattern contained in fd invalid as a file descriptor, and the behavior of line [3] is unspecified.[1]

Because close() “poisons” the bit-pattern of the passed file descriptor, it would be correct to annotate the parameter with [[transfer]].

Example: C free#

The C function free could also be annotated as [[transfer]] on the passed pointer value:

void free(void* [[transfer]] p)

This follows similar behavior to close(): Even though the bits of the pointer are unmodified in the calling scope, the pointer’s value itself has become poisoned and should not be used for any subsequent operation.

Example: amongoc_box_destroy()#

The amongoc_box_destroy() function takes an amongoc_box by-value, and its parameter is annotated with [[transfer]].

Like with close() and free(), the caller’s copy of the passed value is unmodifed, but is still invalidated. Like with close() and free(), subsequent uses of the object are unspecified/undefined, including attempting to destroy the box a second time:

amongoc_box got_box = create_a_box();
amongoc_box_destroy(got_box);         // Okay
amongoc_box_destroy(got_box);         // UNDEFINED BEHAVIOR

Dealing with Transferred Objects#

After passing an object to a [[transfer]] function parameter, the only legal[2] operations on that object are reassignment (replacing the object with a live value) or discarding (letting the object leave scope).

[[nullable]]#

[[nullable]] (documentation attribute)#

A parameter marked with [[nullable]] indicates that passing a null-value for the object is valid. For pointers, this indicates that passing NULL/nullptr has well-defined behavior. Other pointer-like objects may also have a null state, usually their zero-initialized state.

If a parameter is not marked [[nullable]], assume that passing a null argument will introduce undefined behavior.

[[zero_initializable]]#

[[zero_initializable]] (documentation attribute)#

When an aggregate type is annotated with [[zero_initializable]], it indicates that a zero-initialized/empty-initialized instance of that aggregate type is a valid object for certain operations. Usually, this corresponds to a null or zero state. A type that is [[zero_initializable]] should have a Zero-initialized field describing the semantics of such a value.

For such an object, declaring a static instance with no explicit initializer will be valid, as well as initializing using empty braces {}, or using memset to fill its object representation with zero-bytes.

Zero Initialization / Empty Initialization#

Zero initialization (C++) and empty initialization (C) are similar concepts with similar behavior. For convenience, this documentation refers to zero initialization to mean either the C++ concept or empty initialization in C.

In C++ and C23, a trivial aggregate may be initialized with an empty brace pair { } to achieve empt/zero-initialization (this is called value initialization in C++). In prior C versions, initializing with a brace pair and a single literal zero { 0 } will usually achieve the same effect. Some C compilers implement the C23 language feature as an extension in earlier C versions. An object declared static without an explicit initializer will always be zero-initialized at compile time.

[[type(…)]]#

[[type(T)]] (documentation attribute)#

Associates a type with a type-erased object. The meaning of this association depends on the object container type. The following types are often used with [[type(…)]]:

[[storage]]#

[[storage]] (documentation attribute)#

When attached to a pointer parameter, this attribute indicates that the pointer is treated as uninitialized storage for an object of the appropriate type. The API using [[storage]] will not attempt to destroy or read from the pointed-to location.

[[optional]]#

[[optional]] (documentation attribute)#

When applied to a method declaration in a virtual method table, indicates that the associated method pointer may be NULL.

For any methods not declared with [[optional]], assume that the method is required.

Parameter Types#

__type Parameters#

type __type#

Certain function-like macros are documented as functions, and they may be annotated with a __type parameter. This indicates that the corresponding macro argument should be a compile-time type specifier rather than a runtime value.

Unspecified Types#

type __unspecified#

This documentation type indicates a private type that is not part of the public API, although an annotated struct field may be part of the public API.

Dereferencing a pointer-to or accessing the members of an __unspecified type is not guaranteed to have well-defined behavior. A pointer-to-__unspecified should be considered a stronger-typed void.

Documentation Fields#

Field: Allocation#

Some functions are annotated with an Allocation documentation field. This specifies how the function will allocate dynamic memory. If this field is ommitted, either the function does not allocate, uses the standard library allocator, or it accepts an mlib_allocator directly.

Field: Zero-initialized#

The Zero-initialized field appears on some data types and specifies the semantics of that type when it has been zero/empty initialized, if any.

If a type does not have such a field, assume that it has no defined semantics when zero-initialized.

Footnotes