C-binding type signature for pointer or nil

Hi, I’ve been working on bindings for SDL3: GitHub - SleepingInsomniac/sdl3.cr: SDL3 bindings for crystal.

There’s a type signature that accepts a pointer to a c-struct in the renderer bindings:

# extern SDL_DECLSPEC bool SDLCALL SDL_RenderTexture(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_FRect *srcrect, const SDL_FRect *dstrect);
fun render_texture = SDL_RenderTexture(renderer : Renderer*, texture : Texture*, srcrect : FRect*, dstrect : FRect*) : Bool

SDL3’s documentation states that the rect arguments are: “a pointer to the source rectangle, or NULL for the entire ….”

I can’t use : FRect*? or crystal gives an error about only using primitive types.

I’m calling this lib function from my crystal adapter code:

    def render_texture(texture : Texture, source_rect : FRect? = nil, dest_rect : FRect? = nil)
      sr = source_rect.try { |sr| pointerof(sr) } || Pointer(FRect).null
      dr = dest_rect.try { |dr| pointerof(dr) } || Pointer(FRect).null
      LibSdl3.render_texture(@pointer, texture, sr, dr)
    end

Which works, but if I pass the rects directly, I get type errors. For objects like my texture object the c-bindings call #to_unsafe. I would have expected something similar for the c-struct that’s being passed in.

Is there a better way to handle a this?

Could you clarify what kind of type error you get for which specific call?

For example, with a type signature like this:

class Renderer
  # ...
    def render_texture(texture : Texture, source_rect : FRect? = nil, dest_rect : FRect? = nil)
      LibSdl3.render_texture(@pointer, texture, source_rect, dest_rect)
    end

Calling

    renderer.render_texture(texture, nil, Sdl3::FRect.new(x: 0, y: y_offset, w: width, h: height))

raises a type error:

Error: argument 'dstrect' of 'LibSdl3#render_texture' must be Pointer(LibSdl3::FRect), not LibSdl3::FRect

And if I add pointerof:

 278 | LibSdl3.render_texture(@pointer, texture, pointerof(source_rect), pointerof(dest_rect))
                                                 ^
Error: argument 'srcrect' of 'LibSdl3#render_texture' must be Pointer(LibSdl3::FRect), not Pointer(Nil)

Yes. Note that `FRect*` already accepts null pointers, ie `Pointer(FRect).null` is acceptable. So just remove the `?` and it should work.

Yes, that’s the solution I outlined above. I guess my post was more just asking about language ergonomics. That is, if a lib function expects a pointer to a c struct, passing a struct to that method seems like it should automatically convert to a pointer of the type (in the nil case, Pointer(FRect).null)

When creating large-scale bindings, consider defining a wrapper class (value object) in Crystal. This class represents the corresponding C struct. With this design, users work only with Crystal types. This makes implicit conversion between values and pointers easy. If designed properly, users will find it safe and easy to use.

However, this approach adds complexity. You need to carefully consider who allocates the memory. You must decide whether the Crystal class holds a pointer to the struct or holds the struct value itself. You need to clearly distinguish between these cases:

  • When the struct is allocated on the C side and Crystal only holds a pointer
  • When Crystal allocates memory and holds the struct value
  • When both cases are mixed

You also need to consider how to free memory. For example, whether you can use finalize, or whether you should use RAII with blocks.

Therefore, use this approach only when you want to provide high-quality bindings for end users. Crystal users are smart, so it’s also fine to provide only low-level bindings and let them handle memory management themselves.

I hate to ask, but is this response ai generated? If you look at the source, I have plenty of wrapper classes defined that interact with the lib functions, and in fact the question is about a wrapper class method interacting with a lib function.

Half yes, half no. While the text is indeed generated by AI, I first write the response in Japanese, then have Claude proofread and translate it into English. So the content itself is human-made.

And your wrapper class method appeared to take a C-compatible structure as an argument directly.

P.S.:
I found that you’re building many interesting Crystal libraries.
From now on, before replying on reflex, I’ll check the asker’s project first.

1 Like

There’s little you can do. We must take a reference to the struct, otherwise the struct is passed by value. This is identical to C:

SDL_FPRect src = { .x = 0, .y = 0, .w = 10, .h = 10 };
SDL_RenderTexture(renderer, texture, &src, NULL);

Then in Crystal:

src = LibSDL3::FPRect.new(x: 0, y: 0, w: 10, h: 10)
LibSDL3::RenderTexture(renderer, texture, pointerof(src), nil)

If the value is nilable… you must transform it into a Pointer(Rect).null manually or you’ll end up with an invalid pointer such as Pointer(Nil) or Pointer(Rect | Nil) and neither will be the expected memory address or layout.

Yes, this is boring and tedious.

The only alternative I can fathom are method overloads to let the compiler do its job. That may be just as tedious to write:

def render_texture(texture, src : Rect, dst : Rect)
  LibSDL3::RenderTexture(self, texture, pointerof(src), pointerof(dst))
end

def render_texture(texture, src : Rect, dst : Nil)
  LibSDL3::RenderTexture(self, texture, pointerof(src), nil)
end

def render_texture(texture, src : Nil, dst : Rect)
  LibSDL3::RenderTexture(self, texture, nil, pointerof(dst))
end

def render_texture(texture, src : Nil, dst : Nil)
  LibSDL3::RenderTexture(self, texture, nil, nil)
end

Thanks, I actually did have them all implemented as overloads initially. It does seem like the better approach, albeit tedious like you mentioned.