C# structs
A bridgeable C# struct becomes an immutable Kotlin data class: no handle, nothing to close(), and structural ==/hashCode(). A copy crosses the bridge each time, so a change on the Kotlin side is never seen by C# and vice versa; use copy() and pass the result back where you need an update.
The generated struct type is internal, so a function your library exports forward to C# cannot take or return it directly; pass its components as primitives, strings, or enums instead.
Which structs bind
A public, top-level, non-generic C# struct binds one of two ways:
A state constructor. Exactly one public constructor whose parameters cover all stored state, each parameter matching a public property or field of the same type by name (case-insensitively). Kotlin's generated components, and their order, follow that constructor's parameter list. Other public constructors on the same struct bind as alternate constructors.
No such constructor. Every stored field must then be a public settable field, or a public auto-property with a
setorinitsetter. Components, and their order, follow the C# field declaration order. There are no alternate constructors in this shape.
Either way, a component's type must be a primitive, string, a bound enum, or another bridgeable struct (nested, at any depth; see Nested structs).
A struct is skipped, with no generated Kotlin type, if it fails both shapes: for example, a private field with no public component covering it, a readonly public field (an object initializer can't set it), a hand-written property setter (metadata can't prove it writes the field it appears to), or zero stored state. If a nested component itself fails, the whole outer struct is skipped too, and the build warning names the failing component's path, not the outer struct's own rules.
Component order for the no-constructor shape
When a struct has a state constructor, its component order is already public C# API: reordering the parameters breaks C# callers too, so the Kotlin side can't drift silently. When a struct has no state constructor, component order is the field declaration order, which C# callers never see, since they always construct with named properties. Reordering two same-typed fields in the C# source is source-compatible in C# but silently reorders the generated Kotlin data class constructor.
Always use named arguments when constructing this shape from Kotlin:
Members on the struct
Alternate public constructors (state-constructor shape only) become Kotlin secondary constructors. Non-void instance methods and get-only computed properties (not themselves components) become member functions and vals; public static methods land in the companion object. Equals, GetHashCode, ToString, Deconstruct, operators, setters, and void-returning instance methods are not bound; Kotlin's data class keeps its own equality and stringification instead.
Struct-typed properties and methods on classes
A struct works as a parameter, return, or settable property on a bound class's static or instance members, the same as on a top-level function. Cattery is a real handle-backed class with a weigh instance method taking and returning a struct, and a settable struct-typed property, currentProfile:
Cattery itself still needs close() (or use { }), the same as any other handle-backed class; Metrics and Profile are plain values and have none of that.
Nested structs
A struct component can itself be a struct, at any depth. The Kotlin surface stays nested, one val per component, each typed as its own generated data class:
Equality and copy() compose through the nesting with no extra work:
The 22-argument ceiling
Each struct component becomes its own ABI argument (or out-pointer, for a return), and nesting multiplies that count. A member whose flattened argument count, receiver and out-pointers included, exceeds 22 is skipped entirely rather than generated, with a build warning naming the member and the count:
Shrink a nested struct in the signature, or split the member into smaller ones, to bring it back under the ceiling.
Limitations
Nullable<T>components are not supported, including a nullable nested struct.Class-typed (handle) components inside a struct are not supported: a handle doesn't compose with an immutable value copy.
Generic structs and structs as collection elements (
List<Point>) are not supported.A state constructor's parameter nullability is not decoded: a
string?parameter surfaces in Kotlin as non-nullableString. Anullfrom C# there fails fast rather than corrupting memory. The no-constructor shape's components are decoded correctly and are unaffected.