Язык программирования Crystal


You can use function types in C declarations:

lib X
  # In C:
  #    void callback(int (*f)(int));
  fun callback(f : Int32 -> Int32)

Then you can pass a function (a Proc) like this:

f = ->(x : Int32) { x + 1 }

If you define the function inline in the same call you can omit the argument types, the compiler will add the types for you based on the fun signature:

X.callback ->(x) { x + 1 }

Note, however, that functions passed to C can't form closures. If the compiler detects at compile-time that a closure is being passed, an error will be issued:

y = 2
X.callback ->(x) { x + y } # Error: can't send closure
                           # to C function

If the compiler can't detect this at compile-time, an exception will be raised at runtime.

Refer to the type grammar for the notation used in callbacks and procs types.

If you want to pass NULL instead of a callback, just pass nil:

# Same as callback(NULL) in C
X.callback nil

Raises attribute

If a C function executes a user-provided callback that might raise, it must be annotated with the @[Raises] attribute.

The compiler infers this attribute for a method if it invokes a method that is marked as @[Raises] or raises (recursively).

However, some C functions accept callbacks to be executed by other C functions. For example, suppose a ficticious library:

lib LibFoo
  fun store_callback(callback : ->)
  fun execute_callback

LibFoo.store_callback ->{ raise "OH NO!" }

If the callback passed to store_callback raises, then execute_callback will raise. However, the compiler doesn't know that execute_callback can potentially raise because it is not marked as @[Raises] and the compiler has no way to figure this out. In these cases you have to manually mark such functions:

lib LibFoo
  fun store_callback(callback : ->)

  fun execute_callback

If you don't mark them, begin/rescue blocks that surround this function's calls won't work as expected.