Explain memScoped, CPointer, and how you pass a C struct or an out-parameter to a C function from Kotlin/Native.
answer
- memScoped = arena, frees on block exit (even on throw)
- alloc<IntVar>(), .ptr to get CPointer, .value to read
- CPointer<T>.pointed reads fields; null == C NULL
- Out-params: alloc, pass .ptr, read .value after
- By-value structs: cValue { } / CValue + useContents
basics
~20 smemScoped gives you a temporary native memory area that is freed when the block ends. Inside it you allocate C structs and get CPointers to them, which you can pass to C functions, including as out-parameters the C code fills in.
solid answer
~30 sC functions need real native memory, not Kotlin heap objects. `memScoped { }` creates a `MemScope` (an arena); `alloc<T>()` / `allocArray<T>(n)` inside it return native objects whose memory is automatically released when the block exits. A `CPointer<T>` is a typed native pointer; you get one via `.ptr` on an allocated value. To call a C function with an out-parameter, you `alloc<IntVar>()`, pass `.ptr`, then read `.value` after the call. `memScoped` makes lifetime deterministic and exception-safe, avoiding manual `nativeHeap.alloc`/`free` leaks. For read-only struct arguments passed by value you often use `CValue<T>` / `cValue { }` instead.
code
kotlin · 8 linesimport kotlinx.cinterop.*
@OptIn(ExperimentalForeignApi::class)
fun readClock(): Long = memScoped {
val ts = alloc<timespec>() // native struct
clock_gettime(CLOCK_MONOTONIC, ts.ptr) // out-param via CPointer
ts.tv_sec // read field back
}go deeper
Recognizes that C calls need native memory and that memScoped provides it.
Writes a correct out-parameter call: alloc, pass .ptr, read .value, all inside memScoped.
Distinguishes by-pointer vs by-value (CValue/cValue/useContents), explains exception-safe arena lifetime, knows when nativeHeap is required.
Designs allocation strategy for performance and ownership across an FFI boundary, avoiding per-call arena overhead in hot paths.
## The problem C functions operate on **native memory** with C lifetimes. Kotlin objects live on the managed Kotlin/Native heap and can move/be GC'd. So when you call a C function you must hand it **stable native memory** and free it deterministically. ## memScoped — an arena `memScoped { ... }` runs a block with a **`MemScope`** receiver (a memory **arena**). Anything allocated inside is freed **automatically when the block exits**, even on exception. APIs available on the scope: - `alloc<T>()` — allocate one native value of type `T` (e.g. `IntVar`, a struct type). - `allocArray<T>(n)` — allocate a native array of `n` elements. - `T.ptr` — get the `CPointer<T>` to an allocated value. ## CPointer **`CPointer<T>`** is a typed pointer into native memory. Key members: - `.pointed` — the `T` value the pointer references (lets you read/write fields). - `.rawValue` — the raw address. - `[index]` for array pointers. Null is represented by a Kotlin nullable `CPointer<T>?` — a real C `NULL` is `null` in Kotlin. ## Out-parameters Many C functions return data through a pointer argument. Pattern: ```kotlin memScoped { val out = alloc<IntVar>() // native int some_c_function(out.ptr) // C writes into it val result: Int = out.value // read it back } ``` `IntVar` (and `LongVar`, `DoubleVar`, etc.) are the native variable wrappers; `.value` reads/writes the underlying C scalar. ## Passing structs - **By pointer**: `alloc<MyStruct>()`, set fields via `.field = ...`, pass `.ptr`. - **By value**: use `cValue<MyStruct> { field = ... }` producing a `CValue<MyStruct>`, which represents an immutable C value you can pass where the C signature takes the struct by value. `CValue.useContents { }` lets you read its fields. ## Manual heap (rarely) `nativeHeap.alloc<T>()` allocates outside any scope; you **must** `nativeHeap.free(...)` it yourself. Prefer `memScoped` so you can't leak. ## Why it matters `memScoped` ties native lifetimes to a lexical block — deterministic, exception-safe, leak-free — which is the idiomatic way to bridge Kotlin and C memory.
- What happens to memory allocated in memScoped if the block throws?It is still freed; memScoped releases the whole arena on exit regardless of how the block ends, so there's no leak.
- How is a C NULL pointer represented in Kotlin?As a null CPointer<T>?; cinterop maps nullable C pointers to Kotlin nullable pointer types.
- When would you reach for nativeHeap.alloc instead of memScoped?When the allocation must outlive the current block (e.g. handed to C to own); then you're responsible for nativeHeap.free.
memScoped is like a scratch table you set up to talk to C: you lay out the forms (allocs), hand C a pointer to fill in, read the result, and the whole table is cleared the moment you leave the room.
saying these in an interview costs you the question
- Passing a Kotlin object directly to a C pointer parameter
- Calling nativeHeap.alloc without ever freeing it
- Thinking memScoped requires manual free inside the block
- Confusing CPointer (reference) with CValue (by-value)
- Reading out-param via .pointed when .value is needed for scalars