sn-bindgen

Scala 3 Native binding generator to C libraries

Structs are converted to opaque types

For those types, we generate getters, setters, Tag definition, and two apply methods:

  1. A constructor, which takes all the parameters as arguments
  2. An allocator, which only creates an instance on the heap, without initialising it

Source C code

typedef struct {
  long long number;
} Small;

typedef struct {
  int x;
  char* hello;
  Small sm;
} Big;

Generated Scala code

package libtest

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

object structs:
  import _root_.libtest.structs.*

  opaque type Big = CStruct3[CInt, CString, Small]
  
  object Big:
    given _tag: Tag[Big] = Tag.materializeCStruct3Tag[CInt, CString, Small]
    
    // Allocates Big on the heap ??? fields are not initalised or zeroed out
    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]
    
    // Allocates Small on the heap ??? fields are not initalised or zeroed out
    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
    

object types:
    export _root_.libtest.structs.*

object all:
  export _root_.libtest.structs.Big
  export _root_.libtest.structs.Small


You can disable constructor generation

Some bindings (libnotify, gtk, nuklear) have such deep type hierarchies, that apply methods on some structs trigger an exception during bytecode generation (when compiling the generated code):

java.lang.IllegalArgumentException: UTF8 string too large

To work around it, you can disable constructor generation by passing a comma-separated list of struct names using the --render.no-constructor option in CLI, and noConstructor parameter in the Binding(...) specification.

Source C code

typedef struct {
  int x;
  char* hello;
} Enabled;

typedef struct {
  int x;
  char* hello;
} Disabled;

Generated Scala code

package libtest

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

object structs:
  import _root_.libtest.structs.*

  opaque type Disabled = CStruct2[CInt, CString]
  
  object Disabled:
    given _tag: Tag[Disabled] = Tag.materializeCStruct2Tag[CInt, CString]
    
    // Allocates Disabled on the heap ??? fields are not initalised or zeroed out
    def apply()(using Zone): Ptr[Disabled] = scala.scalanative.unsafe.alloc[Disabled](1)
    
    extension (struct: Disabled)
      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
    

  opaque type Enabled = CStruct2[CInt, CString]
  
  object Enabled:
    given _tag: Tag[Enabled] = Tag.materializeCStruct2Tag[CInt, CString]
    
    // Allocates Enabled on the heap ??? fields are not initalised or zeroed out
    def apply()(using Zone): Ptr[Enabled] = scala.scalanative.unsafe.alloc[Enabled](1)
    def apply(x : CInt, hello : CString)(using Zone): Ptr[Enabled] =
      val ____ptr = apply()
      (!____ptr).x = x
      (!____ptr).hello = hello
      ____ptr
    
    extension (struct: Enabled)
      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
    

object types:
    export _root_.libtest.structs.*

object all:
  export _root_.libtest.structs.Disabled
  export _root_.libtest.structs.Enabled


Unions are converted to opaque types

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

Source C code

typedef struct {
  long long number;
} Small;

typedef union {
  int x;
  char* hello;
  Small sm;
} Big;

Generated Scala code

package libtest

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

object structs:
  import _root_.libtest.structs.*
  import _root_.libtest.unions.*

  opaque type Small = CStruct1[CLongLong]
  
  object Small:
    given _tag: Tag[Small] = Tag.materializeCStruct1Tag[CLongLong]
    
    // Allocates Small on the heap ??? fields are not initalised or zeroed out
    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
    

object unions:
  import _root_.libtest.structs.*
  import _root_.libtest.unions.*

  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 = _root_.scala.scalanative.unsafe.alloc[Big](1)
      ___ptr
    
    @scala.annotation.targetName("apply_x")
    def apply(x: CInt)(using Zone): Ptr[Big] =
      val ___ptr = _root_.scala.scalanative.unsafe.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 = _root_.scala.scalanative.unsafe.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 = _root_.scala.scalanative.unsafe.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

object types:
    export _root_.libtest.structs.*
    export _root_.libtest.unions.*

object all:
  export _root_.libtest.structs.Small
  export _root_.libtest.unions.Big


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)

Source C code

typedef struct {
  long long number;
} Small;

long simple(int x, char *y);
Small* with_pointers(Small *x, int y);

Generated Scala code

package libtest

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

object structs:
  import _root_.libtest.structs.*

  opaque type Small = CStruct1[CLongLong]
  
  object Small:
    given _tag: Tag[Small] = Tag.materializeCStruct1Tag[CLongLong]
    
    // Allocates Small on the heap ??? fields are not initalised or zeroed out
    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 _root_.libtest.structs.*
  def simple(x : CInt, y : CString): CLongInt = extern

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


object functions:
  import _root_.libtest.structs.*
  import extern_functions.*
  export extern_functions.*

object types:
    export _root_.libtest.structs.*

object all:
  export _root_.libtest.structs.Small
  export _root_.libtest.functions.simple
  export _root_.libtest.functions.with_pointers


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.

Source C code

typedef struct {
  long long number;
} Small;

void bad_arguments(Small n, Small n2);
Small bad_return_type();

Generated Scala code

package libtest

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

object structs:
  import _root_.libtest.structs.*

  opaque type Small = CStruct1[CLongLong]
  
  object Small:
    given _tag: Tag[Small] = Tag.materializeCStruct1Tag[CLongLong]
    
    // Allocates Small on the heap ??? fields are not initalised or zeroed out
    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 _root_.libtest.structs.*
  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 _root_.libtest.structs.*
  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] = _root_.scala.scalanative.unsafe.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()(__return : Ptr[Small]): Unit =
    __sn_wrap_libtest_bad_return_type(__return)

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

object types:
    export _root_.libtest.structs.*

object all:
  export _root_.libtest.structs.Small
  export _root_.libtest.functions.bad_arguments
  export _root_.libtest.functions.bad_return_type


Generated C code

#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.

Source C code

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

typedef enum {
  X = -1,
  Y = 4,
  Z = 228
} MySigned;

Generated Scala code

package libtest

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

object predef:
    private[libtest] trait _BindgenEnumCInt[T](using eq: T =:= CInt):
      given Tag[T] = Tag.Int.asInstanceOf[Tag[T]]
      extension (inline t: T)
        inline def value: CInt = eq.apply(t)
        inline def int: CInt = eq.apply(t).toInt
    private[libtest] trait _BindgenEnumCUnsignedInt[T](using eq: T =:= CUnsignedInt):
      given Tag[T] = Tag.UInt.asInstanceOf[Tag[T]]
      extension (inline t: T)
        inline def value: CUnsignedInt = eq.apply(t)
        inline def int: CInt = eq.apply(t).toInt
        inline def uint: CUnsignedInt = eq.apply(t)

object enumerations:
  import predef.*
  opaque type MySigned = CInt
  object MySigned extends _BindgenEnumCInt[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)
    def getName(value: MySigned): Option[String] =
      value match
        case `X` => Some("X")
        case `Y` => Some("Y")
        case `Z` => Some("Z")
        case _ => _root_.scala.None
    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 _BindgenEnumCUnsignedInt[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)
    def getName(value: MyUnsigned): Option[String] =
      value match
        case `U_X` => Some("U_X")
        case `U_Y` => Some("U_Y")
        case `U_Z` => Some("U_Z")
        case _ => _root_.scala.None
    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

object types:
    export _root_.libtest.enumerations.*

object all:
  export _root_.libtest.enumerations.MySigned
  export _root_.libtest.enumerations.MyUnsigned


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.

Source C code

typedef void* Cursor;
typedef int (*Visitor)(Cursor*);

Generated Scala code

package libtest

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

object aliases:
  import _root_.libtest.aliases.*
  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 fromPtr(ptr: Ptr[Byte] | Ptr[?]): Visitor = CFuncPtr.fromPtr(ptr.asInstanceOf[Ptr[Byte]])
    inline def apply(inline o: CFuncPtr1[Ptr[Cursor], CInt]): Visitor = o
    extension (v: Visitor)
      inline def value: CFuncPtr1[Ptr[Cursor], CInt] = v
      inline def toPtr: Ptr[?] = CFuncPtr.toPtr(v)

object types:
    export _root_.libtest.aliases.*

object all:
  export _root_.libtest.aliases.Cursor
  export _root_.libtest.aliases.Visitor


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.

Source C code

typedef struct Arr;

typedef struct {
  struct Arr* nested;
} Arr;

Generated Scala code

package libtest

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

object structs:
  import _root_.libtest.structs.*

  opaque type Arr = CStruct1[Ptr[Byte]]
  
  object Arr:
    given _tag: Tag[Arr] = Tag.materializeCStruct1Tag[Ptr[Byte]]
    
    // Allocates Arr on the heap ??? fields are not initalised or zeroed out
    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]]
    

object types:
    export _root_.libtest.structs.*

object all:
  export _root_.libtest.structs.Arr


Global enums are rendered as constants

Source C code

enum {
  HELLO = 25,
  BYEBYE = 11
};

enum {
  HOW=-1,
  DOESTHIS=-2,
  WORK=0
};

Generated Scala code

package libtest

import _root_.scala.scalanative.unsafe.*
import _root_.scala.scalanative.unsigned.*
import _root_.scala.scalanative.libc.*
import _root_.scala.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
  


Anonymous unions/structs are rendered in enclosing companion object

In this example, we have several anonymous definitions as part of the struct.

If the anonymous union/struct/enum is attached to a named field, that field's name will be used as heuristic for naming the anonymous definition.

Otherwise, Union0, Struct1, etc. naming scheme will be used.

In the example below, StructExample.Greeting, StructExample.Test, StructExample.Hello, and StructExample.Union0 will be generated

Deep field access (e.g. calling .x on StructExample directly, like what C allows, is not supported yet, but planned.

Source C code

typedef struct {
  struct {int hello;} greeting;
  union {int x;};
  union {char z;} *test;
  enum {A, B, C} hello;
} StructExample;

Generated Scala code

package libtest

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

object structs:
  import _root_.libtest.structs.*

  opaque type StructExample = CStruct4[StructExample.Greeting, StructExample.Union0, Ptr[StructExample.Test], StructExample.Hello]
  
  object StructExample:
    given _tag: Tag[StructExample] = Tag.materializeCStruct4Tag[StructExample.Greeting, StructExample.Union0, Ptr[StructExample.Test], StructExample.Hello]
    
    // Allocates StructExample on the heap ??? fields are not initalised or zeroed out
    def apply()(using Zone): Ptr[StructExample] = scala.scalanative.unsafe.alloc[StructExample](1)
    def apply(greeting : StructExample.Greeting, _2 : StructExample.Union0, test : Ptr[StructExample.Test], hello : StructExample.Hello)(using Zone): Ptr[StructExample] =
      val ____ptr = apply()
      (!____ptr).greeting = greeting
      (!____ptr)._2 = _2
      (!____ptr).test = test
      (!____ptr).hello = hello
      ____ptr
    
    extension (struct: StructExample)
      def greeting : StructExample.Greeting = struct._1
      def greeting_=(value: StructExample.Greeting): Unit = !struct.at1 = value
      def _2 : StructExample.Union0 = struct._2
      def _2_=(value: StructExample.Union0): Unit = !struct.at2 = value
      def test : Ptr[StructExample.Test] = struct._3
      def test_=(value: Ptr[StructExample.Test]): Unit = !struct.at3 = value
      def hello : StructExample.Hello = struct._4
      def hello_=(value: StructExample.Hello): Unit = !struct.at4 = value
    
    opaque type Greeting = CStruct1[CInt]
    
    object Greeting:
      given _tag: Tag[Greeting] = Tag.materializeCStruct1Tag[CInt]
      
      // Allocates Greeting on the heap ??? fields are not initalised or zeroed out
      def apply()(using Zone): Ptr[Greeting] = scala.scalanative.unsafe.alloc[Greeting](1)
      def apply(hello : CInt)(using Zone): Ptr[Greeting] =
        val ____ptr = apply()
        (!____ptr).hello = hello
        ____ptr
      
      extension (struct: Greeting)
        def hello : CInt = struct._1
        def hello_=(value: CInt): Unit = !struct.at1 = value
      
    
    opaque type Union0 = CArray[Byte, Nat._4]
    object Union0:
      given _tag: Tag[Union0] = Tag.CArray[CChar, Nat._4](Tag.Byte, Tag.Nat4)
      
      def apply()(using Zone): Ptr[Union0] =
        val ___ptr = _root_.scala.scalanative.unsafe.alloc[Union0](1)
        ___ptr
      
      @scala.annotation.targetName("apply_x")
      def apply(x: CInt)(using Zone): Ptr[Union0] =
        val ___ptr = _root_.scala.scalanative.unsafe.alloc[Union0](1)
        val un = !___ptr
        un.at(0).asInstanceOf[Ptr[CInt]].update(0, x)
        ___ptr
      
      extension (struct: Union0)
        def x : CInt = !struct.at(0).asInstanceOf[Ptr[CInt]]
        def x_=(value: CInt): Unit = !struct.at(0).asInstanceOf[Ptr[CInt]] = value
    
    opaque type Test = CArray[Byte, Nat._1]
    object Test:
      given _tag: Tag[Test] = Tag.CArray[CChar, Nat._1](Tag.Byte, Tag.Nat1)
      
      def apply()(using Zone): Ptr[Test] =
        val ___ptr = _root_.scala.scalanative.unsafe.alloc[Test](1)
        ___ptr
      
      @scala.annotation.targetName("apply_z")
      def apply(z: CChar)(using Zone): Ptr[Test] =
        val ___ptr = _root_.scala.scalanative.unsafe.alloc[Test](1)
        val un = !___ptr
        un.at(0).asInstanceOf[Ptr[CChar]].update(0, z)
        ___ptr
      
      extension (struct: Test)
        def z : CChar = !struct.at(0).asInstanceOf[Ptr[CChar]]
        def z_=(value: CChar): Unit = !struct.at(0).asInstanceOf[Ptr[CChar]] = value
    
    opaque type Hello = CUnsignedInt
    object Hello extends _BindgenEnumCUnsignedInt[Hello]:
      given _tag: Tag[Hello] = Tag.UInt
      inline def define(inline a: Long): Hello = a.toUInt
      val A = define(0)
      val B = define(1)
      val C = define(2)
      def getName(value: Hello): Option[String] =
        value match
          case `A` => Some("A")
          case `B` => Some("B")
          case `C` => Some("C")
          case _ => _root_.scala.None
      extension (a: Hello)
        inline def &(b: Hello): Hello = a & b
        inline def |(b: Hello): Hello = a | b
        inline def is(b: Hello): Boolean = (a & b) == b

object constants:
  val A: CUnsignedInt = 0.toUInt
  val B: CUnsignedInt = 1.toUInt
  val C: CUnsignedInt = 2.toUInt
  
object types:
    export _root_.libtest.structs.*

object all:
  export _root_.libtest.structs.StructExample