Working with C: Structs

In this article, we will continue to understand working with external code and look at working with structures.

We will continue working with the project from the previous article with the following structure:

.
├── main.sp
└── mylib
    ├── mylib.sp
    ├── mylib.c.sp
    ├── mylib.c
    └── mylib.h

Let's imagine that the C library has the following structure:

typedef struct { int x; int y; } Point;

It's pretty straightforward, so we won't have any problems.

Now, we need to write a definition of this structure in Spawn. We will use the file mylib.c.sp, where there is already a definition of the _add function:

module mylib extern "C" fn _add(a i32, b i32) -> i32 extern "C" struct Point { x i32 y i32 }

As you can see, the definition of an external structure also begins with the extern keyword.

Now that we already have more than one character, it may be inconvenient for the next ones to write extern "C" every time. To combine all external characters, Spawn has a special extern block:

module mylib extern "C" { fn _add(a i32, b i32) -> i32 struct Point { x i32 y i32 } }

Now adding new external symbols will not require writing extern "C" every time.

Returning to our structure, you may notice that we have defined both fields with type i32. When working with C structures, it is not necessary to define all fields or define them all. Structure fields are only defined when you want to access them from Spawn. If you need one field out of five, then it is not necessary to define all five fields.

Now that we have a structure, let's define the distance function to see how to work with functions that accept C structures:

typedef struct { int x; int y; } Point; int distance(Point a, Point b);
#include "mylib.h" int distance(Point a, Point b) { return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y); }

And define function in Spawn:

module mylib extern "C" { fn _add(a i32, b i32) -> i32 struct Point { x i32 y i32 } fn distance(a Point, b Point) -> i32 }

Now that we have all the necessary functions and structures defined, let’s write a wrapper:

module mylib fn distance(a Point, b Point) -> i32 { return unsafe { distance(a, b) } }

One thing we forgot was to make the Point structure public. Even though the structure is defined outside the Spawn code, in the case of simple structures that store data, it is considered safe to make it public and use it in the module API.

To make a structure public, add the pub keyword before struct:

module mylib extern "C" { // ... pub struct Point { x i32 y i32 } // ... }

Now we are ready to use our library in main.sp:

module main import mylib fn main() { a := mylib.Point{ x: 1, y: 2 } b := mylib.Point{ x: 3, y: 4 } println(mylib.distance(a, b)) // 8 }

Structs without typedef

Often in C code structures are defined without typedef. In this case, for the compiler to be able to find the definition, the structure must have a special attribute #[typedef], in which case the compiler will correctly recognize the structure:

If in our module above the Point structure is defined without typedef:

struct Point { int x; int y; };

Then in Spawn code, the definition of the structure will look like this:

module mylib extern "C" { // ... #[typedef] struct Point { x i32 y i32 } // ... }

Opaque structs

In some cases, a structure may be defined in C code, but its definition is hidden from the user. Such structures are called opaque structs. In this case, the structure definition will be empty in the Spawn code.

Fields with anonymous structs

In C code, it is not uncommon for structures to define anonymous structures within themselves.

typedef struct { int name; struct { int age; int gender; } extra_data; } Data;

In this case, in the Spawn code, the definition of the structure will look like this:

struct ExtraData { age i32 gender i32 } extern "C" struct Data { name i32 extra ExtraData }

Thus, the anonymous structure is defined separately and used within the main structure.

Conclusion

In this article, we looked at how to work with structures from C code. We learned several ways to interop with various types of structures and how to define them in Spawn code.