CodeBlanks Logo Icon CodeBlanks

Callback

Pass callbacks from C into Rust.

Callbacks

Some C functions take a function pointer as a parameter. On the Rust side, that pointer's type is extern "C" fn(..) -> .., and the function it points to must be defined with the C calling convention. The classic example is C's qsort:

use std::ffi::{c_int, c_void};

unsafe extern "C" {
    fn qsort(
        taban: *mut c_void,
        adet: usize,
        boyut: usize,
        karsilastir: extern "C" fn(*const c_void, *const c_void) -> c_int,
    );
}

The comparison function

The callback converts the void pointers it receives into the type it knows and returns negative, zero or positive per the C contract:

extern "C" fn kucukten_buyuge(a: *const c_void, b: *const c_void) -> c_int {
    // SAFETY: qsort only passes addresses of the array's i32 elements.
    let (x, y) = unsafe { (*(a as *const i32), *(b as *const i32)) };
    x.cmp(&y) as c_int
}

The discriminants of Ordering are -1, 0 and 1; that's why as c_int directly gives the value C expects.

Practice Sort with qsort

Sort the array let mut v = [5, 2, 9, 1]; with the qsort declaration and the kucukten_buyuge callback above (v.as_mut_ptr() as *mut c_void, v.len(), size_of::<i32>()) and print it with {:?}.

Starter code

main.rs
use std::ffi::{c_int, c_void};

fn main() {
    // buraya yaz
}

Sample solution

main.rs
use std::ffi::{c_int, c_void};

unsafe extern "C" {
    fn qsort(
        taban: *mut c_void,
        adet: usize,
        boyut: usize,
        karsilastir: extern "C" fn(*const c_void, *const c_void) -> c_int,
    );
}

extern "C" fn kucukten_buyuge(a: *const c_void, b: *const c_void) -> c_int {
    // SAFETY: qsort yalnızca dizideki i32 elemanlarının adreslerini verir.
    let (x, y) = unsafe { (*(a as *const i32), *(b as *const i32)) };
    x.cmp(&y) as c_int
}

fn main() {
    let mut v = [5, 2, 9, 1];
    // SAFETY: işaretçi, adet ve boyut v ile tutarlıdır.
    unsafe {
        qsort(v.as_mut_ptr() as *mut c_void, v.len(), std::mem::size_of::<i32>(), kucukten_buyuge);
    }
    println!("{:?}", v);
}

Expected output

[1, 2, 5, 9]

Questions and answers

Review the lesson questions together with their answers. To test yourself, solve the lesson interactively.

1 What happens if a panic occurs inside a callback?

  • It's ignored
  • It returns to Rust
  • It can't cross the C boundary; the program is aborted Correct answer
  • An error returns to C

Explanation: Unwinding a panic through C code from an extern "C" function used to be undefined; it's now aborted at the boundary. catch_unwind can be used in the callback.

2 C calling convention

Question
____ fn kucukten_buyuge(a: *const c_void, b: *const c_void) -> c_int {
    0
}
Answer
extern "C" fn kucukten_buyuge(a: *const c_void, b: *const c_void) -> c_int {
    0
}

Explanation: A callback given to C is defined with the C calling convention.

3 Item and meaning

Term Meaning
extern "C" fn(..) C function pointer
*const c_void Untyped pointer
x.cmp(&y) as c_int -1, 0, 1
size_of::<i32>() Element size

All lessons in Advanced