SN bindgen

Scala 3 Native binding generator to C libraries
HomeSemantics MotivationQuick startLimitationsConfiguration

Structs are converted to opaque types

For those types, we generate getters, setters, Tag definition, and two constructors, with and without parameters, allocating the struct on the heap.

C header

typedef struct {
long long number;
} Small;

typedef struct {
int x;
char* hello;
Small sm;
} Big;
Generated Scala
package libtest

import scala.scalanative.unsafe.*
import scala.scalanative.unsigned.*
import scalanative.libc.*
import scalanative.*

object types:
opaque type Big = CStruct3[CInt, CString, Small]
object Big:
given _tag: Tag[Big] = Tag.materializeCStruct3Tag[CInt, CString, Small]
def apply()(using Zone): Ptr[Big] = scala.scalanative.unsafe.alloc[Big](1)
def apply(x : CInt, hello : CString, sm : Small)(using Zone): Ptr[Big] =
val ____ptr = apply()
(!____ptr).x = x
(!____ptr).hello = hello
(!____ptr).sm = sm
____ptr
extension (struct: Big)
def x : CInt = struct._1
def x_=(value: CInt): Unit = !struct.at1 = value
def hello : CString = struct._2
def hello_=(value: CString): Unit = !struct.at2 = value
def sm : Small = struct._3
def sm_=(value: Small): Unit = !struct.at3 = value

opaque type Small = CStruct1[CLongLong]
object Small:
given _tag: Tag[Small] = Tag.materializeCStruct1Tag[CLongLong]
def apply()(using Zone): Ptr[Small] = scala.scalanative.unsafe.alloc[Small](1)
def apply(number : CLongLong)(using Zone): Ptr[Small] =
val ____ptr = apply()
(!____ptr).number = number
____ptr
extension (struct: Small)
def number : CLongLong = struct._1
def number_=(value: CLongLong): Unit = !struct.at1 = value

Unions are converted to opaque types

For a union with N members, N constructors will be generated, along with getters and setters.

C header

typedef struct {
long long number;
} Small;

typedef union {
int x;
char* hello;
Small sm;
} Big;
Generated Scala
package libtest

import scala.scalanative.unsafe.*
import scala.scalanative.unsigned.*
import scalanative.libc.*
import scalanative.*

object types:
opaque type Small = CStruct1[CLongLong]
object Small:
given _tag: Tag[Small] = Tag.materializeCStruct1Tag[CLongLong]
def apply()(using Zone): Ptr[Small] = scala.scalanative.unsafe.alloc[Small](1)
def apply(number : CLongLong)(using Zone): Ptr[Small] =
val ____ptr = apply()
(!____ptr).number = number
____ptr
extension (struct: Small)
def number : CLongLong = struct._1
def number_=(value: CLongLong): Unit = !struct.at1 = value
opaque type Big = CArray[Byte, Nat._8]
object Big:
given _tag: Tag[Big] = Tag.CArray[CChar, Nat._8](Tag.Byte, Tag.Nat8)
def apply()(using Zone): Ptr[Big] =
val ___ptr = alloc[Big](1)
___ptr
@scala.annotation.targetName("apply_x")
def apply(x: CInt)(using Zone): Ptr[Big] =
val ___ptr = alloc[Big](1)
val un = !___ptr
un.at(0).asInstanceOf[Ptr[CInt]].update(0, x)
___ptr
@scala.annotation.targetName("apply_hello")
def apply(hello: CString)(using Zone): Ptr[Big] =
val ___ptr = alloc[Big](1)
val un = !___ptr
un.at(0).asInstanceOf[Ptr[CString]].update(0, hello)
___ptr
@scala.annotation.targetName("apply_sm")
def apply(sm: Small)(using Zone): Ptr[Big] =
val ___ptr = alloc[Big](1)
val un = !___ptr
un.at(0).asInstanceOf[Ptr[Small]].update(0, sm)
___ptr
extension (struct: Big)
def x : CInt = !struct.at(0).asInstanceOf[Ptr[CInt]]
def x_=(value: CInt): Unit = !struct.at(0).asInstanceOf[Ptr[CInt]] = value
def hello : CString = !struct.at(0).asInstanceOf[Ptr[CString]]
def hello_=(value: CString): Unit = !struct.at(0).asInstanceOf[Ptr[CString]] = value
def sm : Small = !struct.at(0).asInstanceOf[Ptr[Small]]
def sm_=(value: Small): Unit = !struct.at(0).asInstanceOf[Ptr[Small]] = value

Simple functions are converted to direct @extern functions

Where "Simple" means "not passing naked structs", because Scala Native cannot handle that (passing by pointer is okay)

C header

typedef struct {
long long number;
} Small;

long simple(int x, char *y);
Small* with_pointers(Small *x, int y);
Generated Scala
package libtest

import scala.scalanative.unsafe.*
import scala.scalanative.unsigned.*
import scalanative.libc.*
import scalanative.*

object types:
opaque type Small = CStruct1[CLongLong]
object Small:
given _tag: Tag[Small] = Tag.materializeCStruct1Tag[CLongLong]
def apply()(using Zone): Ptr[Small] = scala.scalanative.unsafe.alloc[Small](1)
def apply(number : CLongLong)(using Zone): Ptr[Small] =
val ____ptr = apply()
(!____ptr).number = number
____ptr
extension (struct: Small)
def number : CLongLong = struct._1
def number_=(value: CLongLong): Unit = !struct.at1 = value

@extern
private[libtest] object extern_functions:
import types.*
def simple(x : CInt, y : CString): CLongInt = extern

def with_pointers(x : Ptr[Small], y : CInt): Ptr[Small] = extern

object functions:
import types.*
import extern_functions.*
export extern_functions.*

Problematic functions generate C forwarders

Where "Problematic" means having a struct as one of its arguments or return type. In this case we generate several variations of public Scala functions, but they all delegate to an external C function which takes its arguments as pointers, and (optionally) returns its value in a provided heap-allocated location.

C header

typedef struct {
long long number;
} Small;

void bad_arguments(Small n, Small n2);
Small bad_return_type();
Generated Scala
package libtest

import scala.scalanative.unsafe.*
import scala.scalanative.unsigned.*
import scalanative.libc.*
import scalanative.*

object types:
opaque type Small = CStruct1[CLongLong]
object Small:
given _tag: Tag[Small] = Tag.materializeCStruct1Tag[CLongLong]
def apply()(using Zone): Ptr[Small] = scala.scalanative.unsafe.alloc[Small](1)
def apply(number : CLongLong)(using Zone): Ptr[Small] =
val ____ptr = apply()
(!____ptr).number = number
____ptr
extension (struct: Small)
def number : CLongLong = struct._1
def number_=(value: CLongLong): Unit = !struct.at1 = value

@extern
private[libtest] object extern_functions:
import types.*
private[libtest] def __sn_wrap_libtest_bad_arguments(n : Ptr[Small], n2 : Ptr[Small]): Unit = extern

private[libtest] def __sn_wrap_libtest_bad_return_type(__return : Ptr[Small]): Unit = extern

object functions:
import types.*
import extern_functions.*
export extern_functions.*
def bad_arguments(n : Ptr[Small], n2 : Ptr[Small]): Unit =
__sn_wrap_libtest_bad_arguments(n, n2)

def bad_arguments(n : Small, n2 : Small)(using Zone): Unit =
val __ptr_0: Ptr[Small] = alloc[Small](2)
!(__ptr_0 + 0) = n
!(__ptr_0 + 1) = n2
__sn_wrap_libtest_bad_arguments((__ptr_0 + 0), (__ptr_0 + 1))

def bad_return_type()(using Zone): Small =
val __ptr_0: Ptr[Small] = alloc[Small](1)
__sn_wrap_libtest_bad_return_type((__ptr_0 + 0))
!(__ptr_0 + 0)

def bad_return_type()(__return : Ptr[Small]): Unit =
__sn_wrap_libtest_bad_return_type(__return)
Generated C
#include <string.h>

void __sn_wrap_libtest_bad_arguments(Small *n, Small *n2) {
bad_arguments(*n, *n2);
};


void __sn_wrap_libtest_bad_return_type(Small *____return) {
Small ____ret = bad_return_type();
memcpy(____return, &____ret, sizeof(Small));
}

Enums are generated for specific C type

Whatever type clang reports for a particular enum - that's the type that will be used for the enum.

This is important, as on Windows enums are int by default, whereas on Linux and OS X they are unsigned int (if there's no negative elements).

This documentation is built on Linux.

C header

typedef enum {
U_X = 1,
U_Y = 4,
U_Z = 228
} MyUnsigned;

typedef enum {
X = -1,
Y = 4,
Z = 228
} MySigned;
Generated Scala
package libtest

import scala.scalanative.unsafe.*
import scala.scalanative.unsigned.*
import scalanative.libc.*
import scalanative.*

object predef:

trait CEnum[T](using eq: T =:= Int):
given Tag[T] = Tag.Int.asInstanceOf[Tag[T]]
extension (t: T) def int: CInt = eq.apply(t)
extension (t: T) def value: CInt = eq.apply(t)


trait CEnumU[T](using eq: T =:= UInt):
given Tag[T] = Tag.UInt.asInstanceOf[Tag[T]]
extension (t: T)
def int: CInt = eq.apply(t).toInt
def uint: CUnsignedInt = eq.apply(t)
def value: CUnsignedInt = eq.apply(t)

object types:
import predef.*
opaque type MySigned = CInt
object MySigned extends CEnum[MySigned]:
given _tag: Tag[MySigned] = Tag.Int
inline def define(inline a: CInt): MySigned = a
val X = define(-1)
val Y = define(4)
val Z = define(228)
extension (a: MySigned)
inline def &(b: MySigned): MySigned = a & b
inline def |(b: MySigned): MySigned = a | b
inline def is(b: MySigned): Boolean = (a & b) == b

opaque type MyUnsigned = CUnsignedInt
object MyUnsigned extends CEnumU[MyUnsigned]:
given _tag: Tag[MyUnsigned] = Tag.UInt
inline def define(inline a: Long): MyUnsigned = a.toUInt
val U_X = define(1)
val U_Y = define(4)
val U_Z = define(228)
extension (a: MyUnsigned)
inline def &(b: MyUnsigned): MyUnsigned = a & b
inline def |(b: MyUnsigned): MyUnsigned = a | b
inline def is(b: MyUnsigned): Boolean = (a & b) == b

Function pointers are defined as opaque types

The inlining in apply method is important - it's a restricting of Scala Native that the function must be statically known.

C header

typedef void* Cursor;
typedef int (*Visitor)(Cursor*);
Generated Scala
package libtest

import scala.scalanative.unsafe.*
import scala.scalanative.unsigned.*
import scalanative.libc.*
import scalanative.*

object types:
opaque type Cursor = Ptr[Byte]
object Cursor:
given _tag: Tag[Cursor] = Tag.Ptr(Tag.Byte)
inline def apply(inline o: Ptr[Byte]): Cursor = o
extension (v: Cursor)
inline def value: Ptr[Byte] = v

opaque type Visitor = CFuncPtr1[Ptr[Cursor], CInt]
object Visitor:
given _tag: Tag[Visitor] = Tag.materializeCFuncPtr1[Ptr[Cursor], CInt]
inline def apply(inline o: CFuncPtr1[Ptr[Cursor], CInt]): Visitor = o
extension (v: Visitor)
inline def value: CFuncPtr1[Ptr[Cursor], CInt] = v

Recursive structs are rewritten with opaque pointers

This is invisible to the user, so doesn't impact the experience, but can complicate reading the code.

Scala cannot have recursive type aliases.

C header

typedef struct Arr;

typedef struct {
struct Arr* nested;
} Arr;
Generated Scala
package libtest

import scala.scalanative.unsafe.*
import scala.scalanative.unsigned.*
import scalanative.libc.*
import scalanative.*

object types:
opaque type Arr = CStruct1[Ptr[Byte]]
object Arr:
given _tag: Tag[Arr] = Tag.materializeCStruct1Tag[Ptr[Byte]]
def apply()(using Zone): Ptr[Arr] = scala.scalanative.unsafe.alloc[Arr](1)
def apply(nested : Ptr[Arr])(using Zone): Ptr[Arr] =
val ____ptr = apply()
(!____ptr).nested = nested
____ptr
extension (struct: Arr)
def nested : Ptr[Arr] = struct._1.asInstanceOf[Ptr[Arr]]
def nested_=(value: Ptr[Arr]): Unit = !struct.at1 = value.asInstanceOf[Ptr[Byte]]

Global enums are rendered as constants

C header

enum {
HELLO = 25,
BYEBYE = 11
};

enum {
HOW=-1,
DOESTHIS=-2,
WORK=0
};
Generated Scala
package libtest

import scala.scalanative.unsafe.*
import scala.scalanative.unsigned.*
import scalanative.libc.*
import scalanative.*

object constants:
val HELLO: CUnsignedInt = 25.toUInt
val BYEBYE: CUnsignedInt = 11.toUInt

val HOW: CInt = -1
val DOESTHIS: CInt = -2
val WORK: CInt = 0