Foreign Function Interfaces
Sec's Foreign Function Interface describes how Sec source code interacts with functions, data and resources defined by a foreign ABI.
extern "C" fn GetVersion() C::int
extern "system" fn GetSystemValue() uint32
The FFI is deliberately an ABI and binding boundary. It does not turn Sec into a C or C++ source-language frontend.
Scope
Sec 0.1 FFI is primarily designed to consume existing C-compatible and platform libraries such as operating-system APIs, libc-like libraries, OpenSSL-style native libraries and similar interfaces.
The Sec compiler does not:
- parse C or C++ header files;
- run a C preprocessor;
- interpret
#define,#ifor#ifdef; - parse C typedef source declarations;
- compile C inline functions;
- import C source expressions.
A separate binding-generation tool may eventually parse foreign headers and generate ordinary Sec FFI declarations, but such a tool is outside the language itself.
General export of arbitrary Sec functions or libraries as user-named C ABI symbols is not part of Sec 0.1. Callback adaptation needed to consume C APIs is supported separately.
Foreign Function Declarations
An imported foreign function is declared with extern and has no Sec body.
extern "C" fn GetVersion() C::int
extern "system" fn GetSystemValue() uint32
This is invalid in Sec 0.1:
extern "C" fn GetVersion() C::int {
return 1
}
The declaration describes a function implemented outside the current Sec source program.
Calling Conventions
The canonical foreign declaration families include:
extern "C"
extern "system"
extern "Sec"
extern "C" uses the active target C ABI.
extern "system" uses the selected platform's system ABI.
extern "Sec" uses the Sec ABI when an external declaration explicitly
requires it.
Unknown calling-convention strings are compile-time errors.
The active CompilationPlan and resolved ABI model determine the final
physical register, stack, aggregate and return classification.
extern and unsafe extern Are Different
Every foreign implementation is outside compiler verification, but that fact does
not automatically require unsafe at every call site.
An ordinary foreign declaration may be called normally when its Sec signature fully expresses the caller contract:
extern "C" fn GetProcessId() C::int
let processId := GetProcessId()
Use unsafe extern when the caller has additional proof obligations that
are not fully expressed or verifiable by the declaration:
unsafe extern "C" fn CopyMemory(
destination: RawPtr[byte],
source: RawPtr[byte],
length: c::stddef::size_t,
) RawPtr[byte]
Calling that function requires an unsafe context:
unsafe {
CopyMemory(destination, source, length)
}
| Declaration | Meaning |
|---|---|
extern |
Foreign/compiler-unverified implementation boundary |
unsafe extern |
Foreign boundary plus additional caller proof obligations |
unsafe never disables ownership, borrowing, effects, cleanup,
target validation or ABI validation.
FFI Describes the ABI, Not the Foreign Source Syntax
A binding author writes the Sec representation required by the foreign ABI.
If a C header uses macros or typedef chains internally, the Sec compiler does not need to reproduce that source structure.
The Sec declaration expresses the resolved ABI contract.
A wrapper may use safer Sec names and domain types as long as the raw declaration remains faithful to the foreign ABI.
Fundamental C ABI Scalar Types
Compiler-known fundamental C scalar types use uppercase C:: qualification.
C::char
C::schar
C::uchar
C::short
C::ushort
C::int
C::uint
C::long
C::ulong
C::long_long
C::ulong_long
C::float
C::double
C::long_double
C::bool
C:: is compiler-known foreign type-family qualification.
It is not ordinary member access and does not imply a runtime object named
C.
Physical width and alignment are determined by the active C ABI model.
For example, C::long may differ between otherwise similar targets.
C Library and Platform Binding Namespaces
Standard-library, implementation-specific and platform-specific C binding types use
lowercase hierarchical c:: qualification.
c::stddef::size_t
c::stddef::ptrdiff_t
c::time::time_t
c::stdarg::va_list
c::posix::socklen_t
These names are not necessarily compiler primitives. Their concrete definitions come from the selected target/library binding environment.
Ordinary identifiers may still use the same spelling:
let mut c: int := 24
let size: c::stddef::size_t := 0
The local variable c does not affect resolution of the foreign namespace.
C and Sec Scalar Types Remain Distinct
Equal physical representation does not create source-level type identity.
let foreignValue: C::int := 42
let secValue: int32 := 42
On a target where both happen to be 32-bit signed integers, they may still be distinct Sec types.
Assignment does not become implicit merely because the current ABI gives both values the same machine representation.
C-to-Sec and Sec-to-C Scalar Conversion
Conversion uses ordinary explicit Sec conversion semantics.
let foreignValue: C::int := 42
let secValue := int32(foreignValue)
let other: int32 := 42
let foreignOther := C::int(other)
If the target type can represent the complete source value domain, conversion is infallible.
If range may be lost, conversion is checked and requires try:
let wide: int64 := ReadWideValue()
let narrow := try C::int(wide)
A representation-identical conversion may lower to no machine instruction. The source-level type conversion remains semantically explicit.
Literal shaping remains separate:
let count: C::int := 10
This is valid when the literal is representable.
ABI-Stable Fixed-Width Sec Scalars
Fixed-width Sec numeric types may appear directly in FFI signatures when they exactly describe the foreign ABI type and the active ABI verifies compatibility.
Examples include:
byte
int8
uint8
int16
uint16
int32
uint32
int64
uint64
float32
float64
For example:
extern "C" fn CRC32(
data: RawPtr[byte],
length: uint32,
) uint32
When the foreign source type is ABI-defined rather than fixed-width, use the foreign type:
C::int
Do not hard-code int32 merely because one current target implements
C int as 32 bits.
Sec bool, char and rune Are Not C Substitutes
Sec language scalars do not automatically stand in for superficially similar C types.
Use:
C::bool
C::char
or the appropriate binding-specific wide-character type when that is what the ABI requires.
Sec rune is not a universal FFI replacement for wchar_t,
UTF-16 units or foreign platform character types.
Raw Pointers at Foreign Boundaries
Use RawPtr[T] when no safe call-bounded Sec borrow contract is established.
extern "C" fn GetCurrent() RawPtr[Device]
A raw pointer:
- may contain a foreign null address;
- does not imply ownership;
- does not establish lifetime;
- does not establish non-nullness;
- does not establish bounds;
- does not establish aliasing guarantees;
- does not establish retention behavior.
Moving or consuming the RawPtr[T] value never means that ownership of
the pointee was transferred.
null Is an Unsafe Foreign/Raw-Pointer Sentinel
null is not an ordinary Sec value.
It may be used only inside an unsafe context explicitly permitted by the unsafe/FFI rules.
unsafe {
if raw is null {
return None
}
}
Null testing uses is.
This is invalid:
unsafe {
if raw == null {
...
}
}
null has no standalone inferred type:
unsafe {
let value := null // invalid
}
A target type may provide raw-pointer context:
unsafe {
let value: RawPtr[Device] := null
}
A numeric zero byte used as a C string terminator is not the foreign null-pointer sentinel.
Safe References in Foreign Parameters
An extern parameter may use ref T or ref mut T when the
foreign contract really is a non-null call-bounded borrow.
extern "C" fn Inspect(value: ref Header) C::int
extern "C" fn Modify(value: ref mut Header) C::int
The contracts are:
| Parameter | Contract |
|---|---|
ref T |
Non-null, shared, valid for the call, not retained by foreign code |
ref mut T |
Non-null, exclusive mutable, valid for the call, not retained by foreign code |
The foreign declaration may still be unsafe extern when additional
proof obligations remain.
unsafe extern "C" fn ProcessBuffer(
first: ref mut byte,
length: c::stddef::size_t,
) void
Here the reference proves the first byte's borrow contract.
It does not prove that length bytes are valid.
Foreign Pointer Returns Use RawPtr[T]
A raw extern declaration must not return ref T or ref mut T.
extern "C" fn GetCurrent() RawPtr[Device]
A returned foreign pointer does not establish a Sec lifetime.
A wrapper must establish nullability, ownership, lifetime and any other foreign invariant before exposing a safe abstraction.
Stored Foreign Pointer Fields Use RawPtr[T]
extern "C" type Node struct {
value: C::int,
next: RawPtr[Node],
}
Stored foreign pointer fields do not use ref or ref mut.
Their nullability, lifetime, aliasing, retention and ownership are properties of the foreign data contract rather than call-bounded Sec borrowing.
C-Compatible Structs
The canonical C struct form is:
extern "C" type Point struct {
x: C::int,
y: C::int,
}
This declares a nominal Sec type whose stored representation matches a C struct under the active C ABI.
The compiler preserves:
- representation-significant source field order;
- natural C ABI field alignment;
- natural C ABI padding;
- nested foreign representation compatibility.
Ordinary behavior may be added through impl.
An ordinary Sec struct is not C-compatible merely because its current fields happen to have matching sizes.
Foreign Struct Representation Does Not Imply C Initialization Semantics
An extern "C" struct is not implicitly Defaultable merely because its
individual fields would have defaults in ordinary Sec.
Complete explicit construction is required unless a dedicated binding/helper defines another valid initialization path:
let point := Point {
x: 10,
y: 20,
}
Omitted foreign fields are errors unless the foreign declaration has an explicit initialization rule that permits omission.
Incomplete C Structs
A bodyless C struct declaration represents an incomplete/opaque C type:
extern "C" type Context struct
Sec does not know the concrete size or stored layout.
Valid uses may include:
RawPtr[Context]
ref Context
ref mut Context
Invalid uses include:
- passing by value;
- returning by value;
- direct construction;
- operations requiring known size or layout.
No separate opaque keyword is required.
C-Compatible Unions
The canonical C union form is:
extern "C" type Value union {
integer: C::int,
real: C::double,
}
An extern "C" union:
- has overlapping C storage;
- has no hidden Sec tag;
- has no compiler-tracked active Sec variant;
- uses the active C ABI layout.
Direct member access is semantically foreign-unsafe because Sec does not know which member is currently active.
The explicit foreign union declaration already marks this representation boundary,
so Sec does not require repetitive explicit unsafe syntax solely for
every member access.
Safe wrappers should hide raw member selection when the foreign protocol supplies a discriminator or another reliable active-member contract.
C-Compatible Enums Are Open
The canonical C enum declaration is:
extern "C" type Color enum {
Red = 1,
Green = 2,
Blue = 3,
}
Representation is selected by the active C ABI.
Unlike an ordinary closed Sec enum, a C enum is open over values representable by the resolved foreign representation.
Foreign code may therefore return an undeclared numeric value.
match color {
Color.Red => HandleRed()
Color.Green => HandleGreen()
Color.Blue => HandleBlue()
_ => HandleUnknownColor()
}
Exhaustive interpretation must account for unknown foreign values.
C Bitfields
A C bitfield uses a C base type followed by a bitfield width:
extern "C" type Flags struct {
enabled: C::uint bit[1],
mode: C::uint bit[3],
reserved: C::uint bit[28],
}
Inside this declaration form, bit[N] is a C bitfield width declarator,
not a standalone Sec type.
The active C ABI determines:
- allocation unit;
- bitfield placement;
- ordering;
- packing;
- alignment effects.
Sec register lsb-first/msb-first semantics do not control
C bitfield layout.
Unnamed bitfields use _:
_: C::uint bit[4],
A zero-width bitfield is valid only when unnamed:
_: C::uint bit[0],
C bitfields are not independently addressable:
ref flags.mode // invalid
ref mut flags.mode // invalid
flags.mode.Ptr // invalid
Assignment uses Sec checked-range semantics rather than silently inheriting C truncation or wrapping behavior.
C bitfields do not imply atomic, volatile, MMIO or thread-safe access.
Fixed C Arrays
A fixed C array stored inside C-compatible data maps to inline fixed-array storage:
extern "C" type Address struct {
bytes: byte[16],
}
The field does not contain a Sec slice or list descriptor.
C array parameters use their actual ABI pointer representation rather than by-value Sec fixed arrays.
C Flexible-Array Members
A C flexible-array member uses:
C::flex[T]
Example:
extern "C" type Packet struct {
length: c::stddef::size_t,
data: C::flex[byte],
}
A flexible-array member:
- is valid only inside an
extern "C"struct; - must be the final stored field;
- has no Sec slice descriptor;
- has no implicit runtime length;
- does not automatically produce a safe slice/view;
- derives usable extent from the foreign allocation/protocol contract.
Packet.SizeOf describes only the fixed C struct portion and excludes
runtime trailing elements.
A wrapper must establish the actual extent before creating a safe view.
Packing, Alignment and Explicit Layout
extern "C" selects ordinary layout under the active C ABI.
Packed, over-aligned, explicitly offset or otherwise overridden layouts use Sec's canonical explicit-layout mechanism.
FFI does not invent a second packing/alignment syntax.
A packed or otherwise misaligned field may use compiler-supported unaligned read/write operations when the target allows them.
Creating ref or ref mut to a misaligned field is invalid
unless the required alignment is proven.
C Function-Pointer Types
A C ABI function-pointer type uses:
C::fn(ParameterTypes) ReturnType
Examples:
C::fn(C::int) void
C::fn(RawPtr[Context], C::int) C::bool
A C function pointer is not a native Sec callable type.
fn(C::int) void // native Sec callable
C::fn(C::int) void // C ABI function pointer
A C function pointer obtained from foreign data may be null.
Calling a possibly-null C function pointer is valid only after non-nullness has been established.
C Callbacks
Sec 0.1 supports callbacks needed to consume foreign C APIs.
The explicit adapter is:
C::callback(callable)
It produces a non-null C::fn(...) R value with the matching C ABI signature.
fn OnValue(value: C::int) void {
ProcessValue(int32(value))
}
let callback := C::callback(OnValue)
The source callable must be:
- reusable;
- environment-free;
- non-capturing;
- compatible with the exact foreign-facing parameter and return types.
Capturing closures cannot be directly adapted to C callbacks in Sec 0.1.
mut fn and -> fn callables are not directly adaptable.
The compiler may generate a private C ABI thunk required to implement the adapter. That is callback interoperation, not general Sec-to-C symbol export.
Stateful Foreign Callbacks Use Foreign Context Pointers
When a C API provides an explicit userdata/context pointer, that pointer is the canonical Sec 0.1 state-transport mechanism.
A binding typically adapts an environment-free Sec callback and passes state through the foreign API's context parameter.
The binding/wrapper remains responsible for:
- context lifetime;
- retention duration;
- thread/callback affinity;
- ownership;
- unregistration ordering;
- preventing callbacks after context destruction.
Sec 0.1 does not silently capture a Sec closure environment behind
C::callback.
C Variadic Functions
C varargs are distinct from native Sec typed variadics.
Native Sec typed variadic:
fn Sum(values: ...int) int {
...
}
C varargs:
unsafe extern "C" fn Printf(
format: RawPtr[C::char],
...
) C::int
A bare final ... is permitted only for C foreign variadic signatures
and C function-pointer types.
A C variadic declaration must be unsafe extern because the fixed signature
cannot express all caller obligations.
The marker must be final.
C Variadic Arguments
Additional C vararg values must already be C-ABI-representable values.
An ordinary Sec scalar must be explicitly converted to the intended C type before it enters a C vararg position.
The compiler then applies the active C ABI's default argument promotions to values that are already C-typed.
Examples include promotion of:
C::floatto the appropriate promoted C floating type;- small C integer types to the appropriate promoted C integer type.
ref and ref mut are not passed directly through C varargs.
Use the correct raw foreign pointer representation when the protocol expects a pointer.
C-compatible aggregates are allowed in varargs only when the active ABI model supports their variadic classification.
Sec spread does not apply to C varargs.
An untyped null cannot be passed as a C vararg because no pointer type
provides the target representation.
c::stdarg::va_list is a target C binding type, never a native Sec variadic pack.
Sec string Is Not a C String
Sec string has no direct C ABI representation.
This raw foreign declaration is invalid:
extern "C" fn Open(path: string) C::int
A C character pointer is represented as an actual pointer type:
RawPtr[C::char]
That pointer does not by itself imply:
- text;
- encoding;
- NUL termination;
- ownership;
- length;
- mutability;
- non-nullness.
String Encoding and Termination Must Be Explicit
FFI defines no universal encoding for C char*.
A binding may define contracts such as:
- UTF-8;
- ASCII;
- locale/code-page text;
- filesystem bytes;
- protocol-specific bytes;
- non-text binary data.
Converting Sec text to NUL-terminated foreign storage must define:
- encoding;
- element type;
- termination rules;
- embedded-zero behavior;
- ownership;
- allocation/storage;
- lifetime.
A generic encoding-agnostic helper named ToCString() is not, by itself,
a complete FFI contract.
Embedded Zero and C String Terminators
A C string terminator is a numeric zero code unit in character storage.
It is not the null pointer sentinel.
If a conversion to NUL-terminated storage rejects embedded zero, that conversion is fallible.
A binding must not silently truncate Sec text at an embedded zero.
Length-Delimited Text and Binary Buffers
Pointer-plus-length APIs are distinct from NUL-terminated string APIs.
extern "C" fn ConsumeText(
data: RawPtr[C::char],
length: c::stddef::size_t,
) C::int
The wrapper must know what the length counts.
Possible units include:
- bytes;
- C character elements;
- UTF-16 code units;
- another protocol-defined element;
- capacity including or excluding a terminator.
Sec must not assume that a foreign length is equal to a Sec string's length property.
Raw declarations use the actual pointer and length parameters. A safe wrapper may accept a slice/view and explicitly derive the raw pointer and length.
Sec performs no hidden slice-to-pointer-plus-length decomposition in raw extern signatures.
Incoming Foreign Strings
A foreign pointer returned as character data remains a raw pointer until a wrapper establishes the complete text contract.
The wrapper must establish:
- nullability;
- readable extent;
- termination or explicit length;
- lifetime;
- ownership;
- encoding.
Decoding invalid foreign text may be fallible.
Wide-character representations are binding-specific.
FFI does not universally equate wchar_t* with UTF-16, UTF-32 or Sec
string.
Fixed and Flexible Character Arrays Remain Arrays
extern "C" type User struct {
name: C::char[64],
}
This field is an inline fixed foreign character array.
It is not automatically a Sec string.
extern "C" type Message struct {
length: c::stddef::size_t,
data: C::flex[C::char],
}
The wrapper decides whether such storage represents text and which encoding/protocol applies.
Pointer-to-Pointer Data Is Explicit
Foreign pointer nesting is represented directly:
RawPtr[RawPtr[C::char]]
It does not automatically become string[].
Before a safe Sec collection can be exposed, the wrapper must know:
- count or sentinel rules;
- element nullability;
- lifetime;
- encoding;
- ownership.
Foreign Resource Ownership
Raw pointers and scalar handles never imply resource ownership by themselves.
An owning foreign resource is represented by an ordinary nominal Sec wrapper:
extern "C" type SSLContextRaw struct
type SSLContext struct {
raw: RawPtr[SSLContextRaw],
}
impl SSLContext {
free {
SSLContextFreeRaw(self.raw)
}
}
The wrapper may become an owner only when the foreign contract establishes that ownership was transferred to Sec.
A borrowed or library-owned foreign pointer must never be silently upgraded to an owning wrapper.
Ownership Transfer to Foreign Code
Foreign ownership transfer uses ordinary Sec ownership semantics on the owning wrapper.
It is not modeled by consuming a RawPtr[T] value and pretending that
ownership of the pointee changed.
A wrapper that consumes an owning Sec resource implements the foreign operation's real success/failure transfer contract.
If foreign code accepts ownership only on success, the wrapper retains ownership until success has been established.
On failure, the wrapper must clean up, return ownership explicitly, or otherwise follow its declared Sec API contract.
Foreign Reference Counting Does Not Create Implicit Copy
A foreign object using retain/release or up-ref/down-ref semantics does not make the Sec wrapper implicitly copyable.
An ordinary Sec copy must not hide a foreign retain operation.
Foreign retain/clone operations are explicit operations and may carry:
- effects;
- failure;
- thread restrictions;
- allocation or external-state mutation.
A resource-owning wrapper with cleanup is move-only by default unless ordinary Sec copy semantics explicitly define an independent safe copy.
Foreign Cleanup and Fallible Close
Lifecycle free performs mandatory cleanup and does not propagate
user-visible errors.
If the foreign resource has a meaningful fallible close, shutdown, flush or commit
operation, expose an explicit Result-returning operation.
After successful explicit close or ownership transfer, the wrapper must prevent double release.
Foreign invalid/sentinel values are binding-specific.
null is only one possible foreign sentinel.
Foreign Failure Conventions Stay Foreign at the Raw Boundary
Raw extern declarations expose the actual foreign protocol.
FFI does not automatically turn these into Result or Option:
- negative integer results;
- status codes;
- null pointers;
- errno-style state;
- Windows-style last-error state;
- OpenSSL-style error queues;
- out-parameter protocols.
A Sec wrapper performs the explicit normalization.
try does not apply to a raw foreign failure convention unless the
declaration itself actually returns an ordinary Sec fallible type.
Auxiliary Foreign Error State
APIs with thread-local or auxiliary error state require binding-specific sequencing.
A wrapper must retrieve the error information before another operation may invalidate it.
Compiler and LSP analyses may use explicit binding contracts to diagnose invalid sequencing.
They must not guess semantics merely from identifiers such as errno,
GetLastError or similarly named functions.
No Unwind Across an Active FFI Boundary
Sec 0.1 does not permit unwind or non-local control transfer to cross an active FFI boundary in either direction.
This includes:
- foreign exceptions crossing Sec frames;
longjmpor equivalent transfer across active Sec frames;- Sec unwind crossing a C callback boundary.
unsafe does not make these transfers legal.
A foreign library that may throw must use a foreign-side shim or equivalent boundary that translates the failure into an ordinary status/error protocol before returning to Sec.
A Sec callback must similarly translate fallible behavior into the callback protocol instead of unwinding through C.
Foreign Effects
Existing Sec effect-guarantee attributes may be attached to extern declarations when the attribute's target rules permit functions.
For ordinary Sec implementations, such guarantees are compiler-verified.
For extern declarations, the same statement is a trusted foreign contract.
The compiler preserves provenance indicating that the fact came from trusted foreign metadata.
Unknown foreign effects are treated conservatively.
unsafe never erases or overrides effect analysis.
Foreign Symbol Names
When the foreign symbol differs from the Sec declaration name, use:
@link_name("foreign_symbol")
extern "C" fn SecName(...) C::int
@link_name changes only the foreign/link symbol.
It does not change:
- the Sec declaration name;
- calling convention;
- safety;
- ownership;
- effects;
- library dependency;
- ABI compatibility.
Native Library and Object Dependencies Belong to the Build Plan
Native libraries, import libraries, frameworks, object files, search paths and equivalent dependencies are project/package/build metadata.
Sec 0.1 does not repeat those dependencies as ordinary per-function FFI annotations.
The active CompilationPlan resolves logical dependencies into concrete
linker inputs for the selected target.
Reusable binding packages may carry their own native dependency metadata through the package/build model.
Dynamic lookup APIs such as dlopen, dlsym,
LoadLibrary and GetProcAddress are ordinary foreign APIs,
not special compile-time linking syntax.
FFI-Representable and FFI-Legal Are Different Questions
The compiler distinguishes:
FFI-representable
the ABI model can describe the physical representation
FFI-legal in this position
Sec FFI rules permit the type in this exact source position
Types commonly legal in appropriate foreign positions include:
- fundamental
C::scalar types; - resolved
c::binding types; - ABI-stable fixed-width Sec numeric scalars;
RawPtr[T];- call-bounded
ref Tandref mut Tparameters; C::fn(...) R;- complete
extern "C"structs; extern "C"enums;extern "C"unions;- fixed arrays stored in foreign data;
C::flex[T]in its restricted final-field position.
Types That Do Not Cross a Raw C/System ABI Directly
The following require another explicit foreign representation or a wrapper:
- Sec
string; - native dynamic collections;
list[T];map[K, V];set[T];- vector/matrix/tensor ownership descriptors;
Option[T];Result[T, E];- interface values;
- ordinary Sec tagged unions;
- ordinary Sec structs without explicit foreign representation;
- native Sec callable values;
- capturing closures;
- unresolved generic templates;
- types with hidden Sec ownership/runtime state and no declared foreign ABI representation.
Sec bool, char and rune also do not
automatically substitute for C boolean/character representations.
Incomplete and Position-Restricted Foreign Types
An incomplete foreign type may appear behind:
RawPtr[T]
ref T
ref mut T
when the foreign contract permits it.
It cannot be passed or returned by value.
ref and ref mut are permitted as call-bounded foreign
parameters, but not as raw foreign returns or stored C pointer fields.
Fixed arrays are valid as inline C data fields but do not represent C array parameters by value.
Generics Must Be Concrete Before ABI Formation
This unresolved generic extern declaration is invalid:
extern "C" fn Process[T](value: T) void
Only concrete monomorphized types can acquire a concrete ABI representation.
Even a concrete generic Sec type is not automatically FFI-compatible. Its concrete representation must independently satisfy the foreign rules for the exact position where it is used.
Target and ABI Validation
FFI legality is validated before target code generation.
The compiler rejects declarations or uses when, for example:
- the calling convention is unavailable;
- the foreign representation is unsupported by the selected ABI;
- an incomplete type is used by value;
- a required foreign layout cannot be represented;
- a C bitfield layout is unsupported by the selected ABI model;
- a variadic aggregate has no legal target classification;
- a C function-pointer signature is unsupported;
- required native dependency metadata cannot be resolved.
Frontend semantics must not hard-code one target's register or aggregate calling rules.
The active ABI model owns physical parameter and return classification.
Safe Sec Wrappers
Raw bindings should normally be hidden behind domain-oriented Sec APIs.
A wrapper may be responsible for:
- null normalization;
- encoding conversion;
- pointer/length validation;
- ownership establishment;
- lifetime establishment;
- cleanup;
- foreign error translation;
- safe references or views;
- domain-specific named types.
For example, a raw declaration may remain very close to the foreign ABI:
extern "C" fn LookupDevice() RawPtr[DeviceRaw]
while the public Sec API exposes an explicit safe abstraction after validating the foreign contract.
A wrapper must never invent ownership, non-nullness, lifetime or encoding that the foreign API does not actually guarantee.
General Sec-to-C Export Is Outside Sec 0.1
Sec 0.1 supports the private C ABI adaptation required by
C::callback.
It does not define a general source mechanism for exporting arbitrary Sec functions or libraries under user-selected C ABI symbol names.
This keeps callback interoperability distinct from a future complete Sec-to-C export model.
Compiler and Lowering Boundary
The AST and Semantic IR preserve FFI semantics until ABI-aware lowering.
Required preserved facts include:
- calling convention;
unsafe externcaller obligations;- foreign symbol name;
- foreign type-family identity;
- C struct/union/enum/incomplete type kind;
- C bitfield width and base type;
- flexible-array membership;
- C function-pointer type;
- callback adaptation;
- C varargs marker;
- raw pointer versus call-bounded reference;
- trusted foreign effect provenance;
- native dependency references;
- source location for diagnostics.
ABI-aware lowering then selects the physical target representation.
Backend register or stack details must never be reverse-engineered to determine the source ownership, safety or foreign type contract.
Diagnostics
FFI diagnostics should identify the foreign declaration, exact parameter/field/return position, calling convention and selected target/ABI when relevant.
Unsafe extern call
calling `CopyMemory` requires an unsafe context
`CopyMemory` is declared `unsafe extern "C"` because the caller must establish
additional memory-range and aliasing obligations
help: validate the required contract and call it inside `unsafe`
Invalid Sec string in raw FFI
Sec `string` has no direct C ABI representation
help: declare the actual foreign pointer/length or terminated-string representation
help: convert through a wrapper that defines encoding, lifetime and ownership
Incomplete foreign type by value
incomplete foreign type `Context` cannot be passed by value
the declaration has no known size or layout
help: use `RawPtr[Context]`, `ref Context`, or `ref mut Context`
when that matches the foreign contract
Foreign pointer return
raw foreign declarations cannot return `ref Device`
a foreign return does not establish a Sec reference lifetime
help: return `RawPtr[Device]` and establish nullability/lifetime in a wrapper
Stored foreign reference
stored C pointer fields must use `RawPtr[T]`
`ref T` and `ref mut T` describe non-retained call-bounded foreign parameters
C callback mismatch
capturing callable cannot be adapted with `C::callback` in Sec 0.1
help: use an environment-free reusable callable
help: pass callback state through the foreign API's userdata/context pointer
Invalid C vararg
C variadic argument is not ABI-representable
help: convert the value explicitly to the intended C ABI type before the call
Other important diagnostics include:
- unknown foreign calling convention;
- extern function with a Sec body;
- invalid
nullcontext; nulltested with equality rather thanis;- ordinary Sec struct used where an explicit C representation is required;
- C bitfield borrowed as an independently addressable field;
- flexible-array member not final;
- native Sec callable used as a C function pointer;
- C variadic declaration not marked
unsafe extern; - foreign unwind across Sec frames;
- foreign representation unsupported by the selected ABI.
Diagnostics should recommend a safe Sec wrapper when raw foreign semantics are leaking into ordinary application code.
Best Practices
- Keep raw ABI declarations separate from domain-oriented Sec APIs.
- Use ordinary
externwhen the signature completely expresses the caller contract. - Use
unsafe externonly when additional caller proof obligations remain. - Use
C::for fundamental C ABI scalar types instead of hard-coding one platform's widths. - Use the appropriate
c::binding namespace for library/platform C typedef families. - Use fixed-width Sec scalars only when the foreign ABI genuinely specifies that fixed width.
- Use
ref/ref mutonly for non-null call-bounded foreign borrows that foreign code does not retain. - Use
RawPtr[T]when nullability, retention, ownership or lifetime cannot be represented as a call-bounded borrow. - Use
extern "C"data declarations for foreign layout; do not rely on accidental ordinary Sec layout. - Keep C union member selection behind a wrapper when a discriminator is available.
- Treat C enums as open foreign value domains.
- Do not borrow C bitfields as independently addressable Places.
- Use
C::callbackonly with reusable environment-free non-capturing callables. - Pass callback state through explicit foreign userdata/context parameters.
- Do not confuse C varargs with native Sec typed variadics or spread.
- Never assume a C character pointer is text without defining encoding, extent and termination.
- Do not make foreign retain/up-ref operations implicit copies.
- Model foreign resources with nominal owning wrappers and deterministic cleanup.
- Translate raw foreign error conventions into
Result/Optiononly in explicit wrappers. - Do not allow unwind or foreign non-local control transfer across active Sec frames.
- Keep native library/object dependencies in project/package/build metadata, not per-function FFI annotations.
Summary
- Sec FFI is an ABI and binding boundary, not a C/C++ source-language subsystem.
- The compiler does not parse C headers, run a preprocessor, compile inline C or import C source expressions.
- General Sec-to-C named symbol export is outside Sec 0.1.
- Imported foreign functions use bodyless
externdeclarations. - Canonical calling conventions include
extern "C",extern "system"andextern "Sec". - The active
CompilationPlanand ABI model determine physical ABI classification. - Ordinary
externdoes not automatically require an unsafe call site. unsafe externmeans that additional caller proof obligations are not fully expressed by the signature.- Calling
unsafe externrequires explicit unsafe context. unsafedoes not disable ownership, borrowing, effects, cleanup or ABI validation.- FFI declarations describe resolved ABI representation, not foreign header source syntax.
- Fundamental C ABI scalars use uppercase
C::qualification. - Platform/library C binding types use lowercase hierarchical
c::qualification. C::intandint32may have equal representation while remaining different Sec types.- C/Sec scalar conversions are explicit and may require
trywhen range may be lost. - Fixed-width Sec numeric scalars may cross FFI directly when they exactly match the foreign ABI contract.
- Use C ABI types for ABI-defined C scalars instead of assuming one target width.
- Sec
bool,charandrunedo not automatically substitute for C boolean or character types. RawPtr[T]is the foreign raw-pointer representation when no safe call-bounded borrow contract exists.- Raw pointers do not establish ownership, lifetime, non-nullness, bounds, aliasing or retention.
nullis an unsafe foreign/raw-pointer sentinel, not an ordinary Sec value.- Null testing uses
is null, not== null. - Untyped standalone
nullhas no inferred type. - Extern parameters may use
ref T/ref mut Tfor non-null call-bounded non-retained foreign borrows. - A raw foreign declaration must not return
ref Torref mut T. - Foreign pointer returns use
RawPtr[T]. - Stored C-compatible pointer fields use
RawPtr[T], not safe references. - C-compatible structs use
extern "C" type Name struct { ... }. - Foreign struct field order is ABI-significant and natural C padding/alignment are determined by the active ABI.
- Ordinary Sec structs are not C-compatible by accidental field-size agreement.
- Foreign structs are not implicitly Defaultable merely because their fields are.
- Bodyless
extern "C" type Name structdeclares an incomplete C type. - Incomplete foreign types may appear behind pointers/references but cannot be passed or returned by value.
extern "C"unions use overlapping untagged C storage.- C union member access is semantically foreign-unsafe but does not require repetitive unsafe syntax solely for each access.
extern "C"enums use the C ABI representation and are open to undeclared representable values.- Matching a C enum must account for unknown values when exhaustiveness matters.
- C bitfields use
field: C-type bit[N]. - The active C ABI determines bitfield layout; Sec register bit-order rules do not.
- C bitfields are not independently addressable.
- C bitfield assignment uses Sec checked-range semantics rather than silent C-style truncation.
- Fixed C arrays inside foreign structs map to inline fixed-array storage.
- C array parameters use the actual ABI pointer form rather than by-value Sec fixed arrays.
- Flexible-array members use
C::flex[T], are final fields and have no hidden Sec descriptor or length. SizeOffor a flexible-array struct covers the fixed portion only.- Explicit packing/alignment uses Sec's canonical layout mechanism rather than a second FFI-specific syntax.
- C ABI function pointers use
C::fn(...) Rand are distinct from native Sec callable types. - C function pointers obtained from foreign data may be null and must be proven non-null before call.
C::callback(callable)adapts a reusable environment-free non-capturing Sec callable to a C function pointer.- Capturing closures,
mut fnand consuming callables are not directly adaptable withC::callbackin Sec 0.1. - Callback adaptation may generate a private C ABI thunk but does not create a general exported C symbol.
- Foreign userdata/context pointers are the canonical Sec 0.1 mechanism for callback state.
- C varargs use a final bare
...and are distinct from native Sec typed variadics. - C variadic declarations must be
unsafe extern "C". - C vararg values must already be C-ABI-representable; ordinary Sec values are explicitly converted first.
- The active C ABI applies default argument promotions to C-typed vararg values.
ref/ref mutare not passed directly through C varargs.- Sec spread does not apply to C varargs.
- Sec
stringhas no direct C ABI representation. - A
RawPtr[C::char]does not by itself imply text, encoding, termination, ownership, length, mutability or non-nullness. - String wrappers must define encoding, terminator rules, embedded-zero behavior, ownership, storage and lifetime.
- A C NUL terminator is a zero character code unit, not the
nullpointer sentinel. - Embedded zero must not silently truncate converted Sec text.
- Pointer-plus-length APIs must define the length unit explicitly.
- Raw extern declarations do not implicitly decompose Sec slices into pointer-plus-length arguments.
- Incoming foreign text remains raw until nullability, extent, termination/length, lifetime, ownership and encoding are established.
- Foreign fixed/flexible character arrays remain arrays, not automatic Sec strings.
RawPtr[RawPtr[C::char]]does not automatically become a Sec string collection.- Raw pointers and scalar foreign handles never imply ownership.
- Owning foreign resources are represented by nominal Sec wrapper types.
- A borrowed/library-owned foreign pointer must not be silently upgraded to an owning wrapper.
- Ownership transfer to foreign code is expressed through ordinary Sec ownership of the wrapper, not by consuming a raw pointer.
- Foreign success/failure transfer semantics must be implemented explicitly by the wrapper.
- Foreign reference counting does not make the Sec wrapper implicitly Copyable.
- Foreign retain/clone operations are explicit and may have effects or failure behavior.
- Mandatory lifecycle cleanup belongs in
free; meaningful fallible close/shutdown/commit uses an explicit Result-returning operation. - FFI does not automatically translate foreign status/null/errno/out-parameter protocols into
ResultorOption. trydoes not understand an undeclared foreign error convention.- Auxiliary error-state sequencing is binding-specific and must be explicit.
- No unwind or non-local control transfer may cross an active FFI boundary in either direction in Sec 0.1.
- Foreign effect guarantees are trusted contracts whose provenance remains visible to the compiler.
- Unknown foreign effects are conservative.
@link_namechanges only the foreign symbol name.- Native library/object/framework/search-path dependencies belong to project/package/build metadata.
- Per-function native library dependency annotations are not the Sec 0.1 model.
- The compiler distinguishes physical FFI representability from legality in a specific source position.
Option,Result, Sec strings, ordinary structs/unions, interfaces, native callable values, capturing closures and ownership-bearing collection descriptors do not cross a raw C/system ABI directly without an explicit foreign representation.- Unresolved generic extern declarations are invalid.
- Concrete generic types must independently satisfy the relevant FFI representation rules.
- FFI validation is target-aware and occurs before code generation.
- The active ABI model owns physical parameter and return classification.
- Raw bindings should normally be wrapped by safe/domain-oriented Sec APIs.
- Safe wrappers must never invent foreign ownership, lifetime, encoding or non-nullness guarantees.
- Semantic IR preserves FFI contracts until ABI-aware lowering chooses the concrete physical representation.