Side point: ftw(3) is much more interesting unix API to call from some FFI layer than qsort(3). And I spent about a year pestering people from Sun with you should implement fts_open(3) and friends because it presents more sane API for FFIs for the same functionality.
It seems Solaris has been adding many BSD and, especially, Linux compatibility APIs lately. It seems too little, too late; or perhaps the initiative is part of their effort to EoL Solaris, providing an upgrade path to Linux.
There's really no reason to pass a rust closure to `qsort` instead of sorting in Rust. That said, if there's demand for real world use cases that require passing Rust closures to C APIs that take only a function pointer and not a data pointer, I'll be happy to write a follow up.
That's still true even if the API takes a separate context pointer that is given to your function as an argument.
There is still a function pointer there, and what you'd like to use as a function pointer is a function in your local language, and that's an object with an environment. Even if some instances of it have no environment, the FFI mechanism might want to tack on its own. For instance, the FFI needs to be able to route the callback to the appropriate function. Whatever function pointer FFI gives to the C function, when the C library calls that function, FFI has to dispatch it back to the original high level function. That requires context. Now that context could be shoehorned into that context parameter, but it could be inconvenient to do so; that parameter belongs to the program and to the conversation that program is having with the C API.
- is inherently slow, because CPUs have separate data and instruction caches;
- is extra slow in practice because you need a separate allocation for executable memory (unless your stacks and heap are RWX, which is a terrible idea);
- is not portable, requiring architecture- and OS-specific code; and
- is not supported at all in many environments (of varying levels of braindeadness).
For a statically compiled language like Rust, it makes much more sense to use the context pointer.
(qsort is really only for C. Other languages can potentially inline the comparison function, so using FFI for that is kind of insane.)
Problem solved.
Roughly speaking:
thread_local! {
static CBQ: Option<Box<impl FnMut(i32, i32) -> i32>>;
}
#[no_mangle]
extern "C" fn qsort(array: *mut i32, val: usize, callback: impl FnMut(i32, i32) -> i32);
pub fn rust_qsort(array : Vec<i32>, callback: impl FnMut(i32, i32) -> i32){
CBQ.replace(Box::new(callback)).unwrap_none();
unsafe {
qsort(array.as_mut_ptr(), array.len(), &rust_qsort_callback);
}
CBQ.take().unwrap();
}
fn rust_qsort_callback(a: *mut i32, b: *mut i32) -> i32 {
let callback = CBQ.take().unwrap();
let (a, b) = unsafe {
(*a, *b)
};
let result = callback(a, b);
CBQ.replace(callback).unwrap_none();
result
}
fn main() {
let a = vec![4,5,6,3,2];
rust_qsort(a, |a, b| {
if a < b {
-1
} else if a > b {
1
} else {
0
}
})
}
ought to work. (There's some fun with generics and panics, which is some fun to solve, but nothing which breaks the premise above). wrapped_qsort(/* array,callback */)
{
auto tmp = CBQ;
CBQ = wrap(callback);
qsort(array.ptr,array.len,cbq_callback);
CBQ = tmp; /* pop old value from stack */
}It won't fail recursively - while the second call is happening, the first will be stored on the stack (see the take and replace in the callback shim function)
This is the TXR Lisp interactive listener of TXR 228.
Quit with :quit or Ctrl-D on empty line. Ctrl-X ? for cheatsheet.
1> (with-dyn-lib nil
(deffi qsort "qsort" void ((ptr (array wstr)) size-t size-t closure))
(deffi-cb qsort-cb int ((ptr wstr-d) (ptr wstr-d))))
#:lib-0005
2> (let ((vec #("the" "quick" "brown" "fox"
"jumped" "over" "the" "lazy" "dogs")))
(prinl vec)
(qsort vec (length vec) (sizeof wstr)
[qsort-cb (lambda (a b) (cmp-str a b))])
(prinl vec))
#("the" "quick" "brown" "fox" "jumped" "over" "the" "lazy" "dogs")
#("brown" "dogs" "fox" "jumped" "lazy" "over" "quick" "the" "the")
#("brown" "dogs" "fox" "jumped" "lazy" "over" "quick" "the" "the")
The lambda is pointless; we could create the FFI closure directly from cmp-str with [qsort-cb cmp-str]. It shows more clearly that we can use any closure.