Strings, str and Slices

This chapter will cover the Rust String type that crosses to the C-side as EzffiString, and the two fat-pointer types ezffi ships by default: EzffiStr (&str) and EzffiSlice (&[T]). Both fat pointers point to borrowed data, so they don't have a free function, and they're always passed by value. EzffiString and EzffiStr are defined in the ezffi/string.h header, and EzffiSlice in ezffi/slice.h.

String

Rust Strings are wrapped the same way non C-compatible structs are wrapped (a c_void pointer). This may change in the future in favor of a more performant solution. What makes String special is that ezffi makes it easy to work with it using C-like strings, by exporting the following functions in the ezffi/string.h header:

void ezffi_string_free(const struct EzffiString *o);

struct EzffiString ezffi_string_new(void);
struct EzffiString ezffi_string_with_capacity(uintptr_t capacity);
struct EzffiString ezffi_string_from(struct EzffiStr s);
uintptr_t ezffi_string_len(const struct EzffiString *this_);
bool ezffi_string_is_empty(const struct EzffiString *this_);
uintptr_t ezffi_string_capacity(const struct EzffiString *this_);
void ezffi_string_clear(struct EzffiString *this_);
void ezffi_string_reserve(struct EzffiString *this_, uintptr_t additional);
void ezffi_string_truncate(struct EzffiString *this_, uintptr_t new_len);
void ezffi_string_shrink_to_fit(struct EzffiString *this_);
void ezffi_string_push_str(struct EzffiString *this_, struct EzffiStr s);
void ezffi_string_insert_str(struct EzffiString *this_, uintptr_t idx, struct EzffiStr s);
bool ezffi_string_eq_str(const struct EzffiString *s, struct EzffiStr other);

EzffiStr

This is the key that makes Rust String easy to work with using C strings. EzffiStr is the C-type for &str, it's defined in ezffi/string.h, and its definition looks like:

typedef struct EzffiStr {
  uintptr_t len;
  uint8_t *ptr;
} EzffiStr;

and can be built in the C-side using the two following macros:

// Note that the first macro uses sizeof() to get the length and the second one
// uses strlen(), stopping in the first \0 byte it founds

#define EZFFI_STR(lit) ((EzffiStr){ .ptr = (uint8_t*)(lit), .len = sizeof(lit) - 1 })
#define EZFFI_CSTR(cstr) ((EzffiStr){ .ptr = (uint8_t*)(cstr), .len = strlen(cstr) })

It's important to remember: if EzffiStr is built on the C-side and you allocated heap memory for the bytes it points to, you must ensure that memory is deallocated using the C free() function once EzffiStr is no longer in use. The struct points to borrowed data — it doesn't own it.

EzffiSlice

Almost the same concept as EzffiStr but with a slightly different definition, and defined in ezffi/slice.h:

typedef struct EzffiSlice {
  void *ptr;
  uintptr_t len;
} EzffiSlice;

EzffiSlice is the C-type for any &[T]. It points to borrowed data so it doesn't have a free function, and it's the user's responsibility to ensure the data lives at least as long as the EzffiSlice. The user can manipulate the data using pointer arithmetic and casting the void* to the contained type. It's the user's job to ensure the cast is valid.

EzffiSlice can also be built on the C-side using the two following macros:

// Again, the difference is in how the len is being obtained

#define EZFFI_SLICE(p, n) ((EzffiSlice){ .ptr = (void*)(p), .len = (n) })
#define EZFFI_SLICE_FROM_ARRAY(arr) ((EzffiSlice){ .ptr = (void*)(arr), .len = sizeof(arr) / sizeof((arr)[0]) })