How to pass (null pointer) or (struct that contains null pointers) through FFI to C?

Hey Rust community!

I'm having trouble re-assigning pointer from null (set on the Rust side) to a new valid address (on the C side).

In short, I have a Rust struct that exists in a share library, which I would love to exposed to my C main program through FFI.

Here's the small Rust library contains the static struct, out_rust_stdout_plugin. Notice that the proxy is a null pointer and _head is a struct that only contains null pointers:

use std::ffi::c_void;
use std::os::raw::{c_int, c_char};
use std::ptr;

// #[repr(C)] pub struct Foo { _private: [u8; 0] }

pub struct embedded_list {
    pub prev: *mut embedded_list,
    pub next: *mut embedded_list,

pub struct simple_s {
    pub name: *const c_char,
    pub type_: c_int,
    pub proxy: *mut c_void,
    pub _head: embedded_list,
    pub cb_init: Option<
        extern "C" fn() -> c_int

#[export_name = "out_rust_stdout_plugin"]
pub static OUT_STDOUT2_PLUGIN: simple_s = simple_s {
    name: "strrr\0".as_ptr() as *const c_char,
    type_: 20,
    proxy: ptr::null_mut(),
    cb_init: Some(plugin_init),
    _head: embedded_list {
        prev: ptr::null_mut(),
        next: ptr::null_mut(),

extern "C" fn plugin_init() -> c_int {
    println!("init called!!");

// Globally mutable struct variable is inherently unsafe. So
// we have to convince the Rust compiler that the simple_s
// type is thread-safe (uphold from the C main program).
unsafe impl Sync for simple_s {}

Here's the tiny C program that loads the symbox, out_rust_stdout_plugin from the Rust share library. It then tries to rebind output->_head.prev or output->proxy to new addresses. Both ended up with segmentation faults:

#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>

struct embedded_list {
    struct embedded_list *prev, *next;

struct simple_s {
    char *name;
    int type;
    void *proxy;
    struct embedded_list _head;
    int (*cb_init) ();

int main() {
    void *handle;
    handle = dlopen("/usr/local/lib/", RTLD_LAZY);
    if (!handle) {
        printf("handle is null\n");
        return -1;

    void *s;
    s = dlsym(handle, "out_rust_stdout_plugin");
    if (dlerror() != NULL) {
        printf("dlsym is null\n");
        return -1;

    if (!s) {
        printf("can not load plugin\n");
        return -1;

    struct simple_s *output;
    output = (struct simple_s *) s;
    printf("output->name: %s\n", output->name);
    printf("output->name ptr: %p\n", output->name);
    printf("output->type: %d\n", output->type);
    printf("output->cb_init: %p\n", output->cb_init);
    printf("output->proxy: %p\n", output->proxy);
    printf("output->_head: %p\n", output->_head);
    printf("&output->_head: %p\n", &output->_head);
    printf("cb_init execution: %d\n", (output->cb_init)());
    // attempt to rebind the pointer
    struct embedded_list *embedded_l_ptr = (struct embedded_list*) calloc(1, sizeof(struct embedded_list));
    printf("valid embedded: %p\n", embedded_l_ptr);
    output->_head.prev = embedded_l_ptr; // seg fault!

    // the following will also segfault when the pointer assignment:
    // output->_head.prev = embedded_l_ptr;
    // is commented out:
    // int i = 3;
    // output->proxy = &i;
    printf("probably fail\n");
    return 0;

Sorry if this is super obvious, I've been banging my head all day on this and has not been able to figure out why.

I suppose the problem is that Rust think that data is const and move it to read only memory, so
when you attempt to write to read-only memory from C you got segfault.
Try this change: pub static mut OUT_STDOUT2_PLUGIN

1 Like

THANK YOU!!!!!! Duh! It's indeed something obvious ha! For those who are curious, here's a little more information on read-only memory:

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.