What is the equivalent of c's void* pointer and function pointer in rust

I'm working on a porting a c library to rust and I see function pointers and also void pointers. What are their equivalents in Rust?

Example of void pointer:

/* A nsync_dll_element_ represents an element of a doubly-linked list of waiters. */
typedef struct nsync_dll_element_s_ {
	struct nsync_dll_element_s_ *next;
	struct nsync_dll_element_s_ *prev;
	void *container; /* points to the struct this nsync_dll struct is embedded in. */
} nsync_dll_element_;

Example of function pointer:

struct wait_condition_s {
	int (*f) (const void *v);
	const void *v;
	int (*eq) (const void *a, const void *b);
};

What are the performant rust equivalents? Any help is much appreciated.

void pointers:

C:

typedef struct nsync_dll_element_s_ {
	struct nsync_dll_element_s_ *next;
	struct nsync_dll_element_s_ *prev;
	void *container; /* points to the struct this nsync_dll struct is embedded in. */
} nsync_dll_element_;

rust:

use core::ffi::c_void;

#[repr(C)]
struct nsync_dll_element {
	next: *mut nsync_dll_element,
	prev: *mut nsync_dll_element,
	container: *mut c_void;
}

CAUTION: because of the borrow semantic, linked list in rust is infamously HARD to implement correctly, intrusive linked list is especially VERY HARD. if you just want to "mechanically" translate some C code involving intrusive linked list into rust, please don't, you'd better rewrite the logic in rust, or just call the C code through ffi.

function pointers:

C:

struct wait_condition_s {
	int (*f) (const void *v);
	const void *v;
	int (*eq) (const void *a, const void *b);
};

rust

#[repr(C)]
struct WaitCondition {
    f: fn(v: *const c_void) -> i32,
    v: *const c_void,
    eq: fn(a: *const c_void, b: *const c_void) -> i32,
}
4 Likes

Thank you for replying.

What is the difference between c_void and () zero sized type. Can they be used interchangeably?

Found this crate for intrusive doubly linkedlist: intrusive_collections::linked_list - Rust

for raw pointers, yes. for rust references, no.

rust raw pointers can be casted to different types, the type is mostly for convenience. if all you need is an opaque pointer that can be stored and passed as-is to some ffi API, you can use raw pointer of any type (but must be Sized, i.e. size is known at compile time, this includes zero sized types), rust doesn't care about the type of raw pointers if you don't deref them (requires unsafe anyway).

1 Like

The goal is to do a pure Rust port with no ffi. That is no functionality from C implementation. The C library builds its own synchronization primitives like semaphore, condvar, mutex etc.

Thank you for the answers. This clarifies things a bit.

Why? Mechanically translated code is worse than C!

No, really, that's by design, it's not a bug.

“Safe” rust is much safer and robust than C, but warts have to live somewhere… and they live in unsafe Rust.

That means that unsafe Rust is harder and more fragile than C.

And that means that mechanical translation from C to Rust makes things worse, not better.

In Rust you rarely need or want to do that. First of all it's much easier to bring crates into Rust project that libraries into C project. Second – all that stuff is, by necessity, built in unsafe Rust thus you don't want to do that except you don't have any choice (see above).

Then it's usually better to think about what you library does.

In Rust you wouldn't have any “pointers to void” here or “functions”. You would have closure, generics, traits and other such things.

How much Rust background do you have? Porting C library to Rust when you don't have enough Rust knowledge like some people are doing is extremely efficient way to develop intense hatred of Rust for the remaining of your life.

It's roughly similar to an attempt to port assembler library, full of machine-code-level tricks to C: not impossible… if you know C pretty well – but, essentially, unfeasible, for someone like Mel.

2 Likes

I'm working on this port to increase my Rust knowledge and understand the intricacies of Rust. How else would I do that? Learn by doing, right?

I understand why everyone is warning me against doing this. But I want do this for the sake of learning.

I've intermediate Rust experience. I dabbled with unsafe and it took me a while to debug a lifetime issue (Miri FTW).

If it's for the sake if learning then it could be an interesting project and may even lead to something viable.

Just be prepared to redo things few times.

That's, essentially, why that's not recommended to do in “real” work: it's usually not easy to convince management to give you a chance to redo the same work 2-3-5 times… but that's, essentially, necessity to do a good port of C library in Rust if you don't have few years of Rust experience already.

1 Like

The function pointer should be wrapped in an Option. This is because function pointers in C can be null, but Rust function pointers cannot be. See https://doc.rust-lang.org/std/option/index.html#representation

Also the abi matters. If you need it to work with both functions from C and Rust you need to declare it as:

    eq: Option<unsafe extern "C" fn(a: *const c_void, b: *const c_void) -> i32>,

This also means any functions you want to put in that field also has the unsafe extern "C" in the signature.

As for the struct, you might prefer to do something like:

#[repr(C)]
struct nsync_dll_element<T> {
	next: *mut T,
	prev: *mut T,
	container: *mut c_void,
}

But as others have mentioned. If you're able to avoid doing this by just using another type from std::collections then prefer that.

As for learning I recommend reading both the rust reference and the rustonomicon, even though some say it's outdated. It's a lot to read both, but I think it's necessary when you're writing more unsafe code.

2 Likes