skip to content

Contrast CValue<T> with CPointer<T>: when do you use cValue { } and useContents, and why does the distinction matter for C functions that take or return structs by value?

level: seniorimportance: nice to knowfreq 22%

answer

  1. CPointer = mutable reference to native mem (scope-bound)
  2. CValue = immutable by-value struct snapshot (scope-free)
  3. cValue { } builds it; useContents { } reads its fields
  4. By-value C signature → CValue; pointer signature → CPointer
  5. readValue() turns a pointed struct into a CValue

basics

~20 s

CPointer<T> is a reference to native memory you can mutate; CValue<T> is an immutable copy of a C struct passed by value. You build one with cValue { } and read its fields with useContents { }. You use CValue when the C function takes or returns the struct itself, not a pointer.

solid answer

~40 s

A **`CPointer<T>`** points at live native memory (from `alloc`/`allocArray` in a `memScoped`), so the callee can read and write through it — ideal for out-parameters and shared buffers. A **`CValue<T>`** represents a C **struct passed by value** — an immutable, self-contained snapshot, not a pointer. You construct one with **`cValue<T> { field = ... }`** and read it via **`useContents { }`** (which materializes it into a scope so you can access fields). When a C signature is `void f(struct S s)` or `struct S g()`, cinterop uses `CValue<S>`; when it's `void f(struct S* s)`, you pass a `CPointer<S>`. The distinction matters for correctness (mutation visibility), ABI (by-value structs follow C calling conventions), and lifetime (CValue is copy-semantic and scope-free).

code

kotlin · 14 lines
kotlin
import kotlinx.cinterop.*

@OptIn(ExperimentalForeignApi::class)
fun demo() {
    // by-value: build + read with cValue/useContents
    val p = cValue<CGPoint> { x = 3.0; y = 4.0 }
    val len = p.useContents { kotlin.math.hypot(x, y) }

    // by-pointer: mutable, needs a scope
    memScoped {
        val r = alloc<CGRect>()
        r.origin.x = 0.0          // callee could mutate via r.ptr
    }
}

go deeper

for a junior

Knows CPointer is a reference and CValue is a by-value struct copy.

for a middle

Uses cValue { } to build and useContents { } to read, and picks the right one based on the C signature.

for a senior

Explains ABI/calling-convention and lifetime reasons, and bridges via readValue()/useContents().ptr correctly.

for a principal

Designs hot-path interop avoiding needless copies vs arena allocation, weighing CValue immutability against CPointer mutation needs.

## Two representations of a C struct C has two ways to pass a struct: 1. **By pointer** — `void f(struct S* p)`. The callee can mutate the caller's struct. 2. **By value** — `void f(struct S s)` / `struct S g(void)`. A copy crosses the boundary; the callee can't affect the original. Kotlin/Native models these with **two different types**. ## CPointer<T> - A typed **pointer** to native memory. - Obtained from `memScoped { val s = alloc<S>(); s.ptr }` or `allocArray`. - **Mutable through the pointer**: `s.field = ...`, and the callee's writes are visible to you. - Tied to a memory **scope/lifetime** — the arena frees it. - Use for **out-parameters**, buffers, and APIs that take `S*`. ## CValue<T> - An **immutable by-value** representation of a struct — conceptually a snapshot, **not** an address. - Built with **`cValue<S> { /* this is an S, set fields */ }`**. - Read fields with **`useContents { /* this: S */ }`**, which places the value into a temporary scope so you can access its members and return a result. - No manual scope needed — it carries its own storage; copy semantics. - Use when the C signature **takes or returns the struct by value**. ```kotlin import kotlinx.cinterop.* @OptIn(ExperimentalForeignApi::class) fun makePoint(): CValue<CGPoint> = cValue<CGPoint> { x = 1.0; y = 2.0 // 'this' is a CGPoint } @OptIn(ExperimentalForeignApi::class) fun sumPoint(p: CValue<CGPoint>): Double = p.useContents { x + y } ``` ## Why the distinction matters - **Mutation visibility**: pass a `CPointer` if you need the C function's writes back; a `CValue` is a copy, so writes don't propagate. - **ABI correctness**: by-value structs use the platform C **calling convention** (registers/stack rules). cinterop chooses `CValue` so the call is laid out correctly; you can't substitute a pointer. - **Lifetime**: `CPointer` lives only as long as its arena; `CValue` is self-contained and can be stored/returned without a `memScoped`. - **Returning structs**: a C function `struct S g(void)` returns a `CValue<S>`; you must `useContents` (or feed it onward) to read it. ## Bridging between them - `CValue.useContents { this.ptr ... }` exposes a temporary pointer to the value within the block. - A `CPointer<S>.pointed.readValue()` produces a `CValue<S>` snapshot of pointed-to memory. Choosing the right one is about matching the **C signature's by-value vs by-pointer contract** — get it wrong and you either lose mutations or mis-call the ABI.

  • How do you read fields out of a CValue<T>?
    Call useContents { }, where 'this' is the materialized struct, and return whatever you computed from its fields.
  • A C function returns a struct by value. What Kotlin type comes back?
    A CValue<T>; you use useContents (or pass it onward) to access the contents — there's no pointer to dereference.
  • Can you mutate a CValue and have the change seen by C?
    No; CValue is an immutable copy. For mutation visible to C you need a CPointer into shared native memory.

CPointer is a shared whiteboard both sides can edit; CValue is a photocopy you hand over — the receiver can't change your original.

saying these in an interview costs you the question

  • Using a CPointer where the C signature passes the struct by value
  • Expecting mutations to a CValue to propagate back
  • Trying to read CValue fields without useContents
  • Allocating a struct in memScoped just to return it (vs CValue)
  • Confusing readValue() (snapshot) with .pointed (live reference)

context