You can use function types in C declarations:
lib X
  # In C:
  #
  #    void callback(int (*f)(int));
  fun callback(f : Int32 -> Int32)
end
Then you can pass a function (a Proc) like this:
f = ->(x : Int32) { x + 1 }
X.callback(f)
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
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
end
LibFoo.store_callback ->{ raise "OH NO!" }
LibFoo.execute_callback
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 : ->)
  @[Raises]
  fun execute_callback
end
If you don't mark them, begin/rescue blocks that surround this function's calls won't work as expected.