5

We have a legacy C/C++ library in which we are trying to integrate some Rust code as a C style library. However, this C/C++ library should also be usable in Rust through a wrapper written in Rust.

The rust and C/C++ library works as expected. The build of the rust wrapper is successful, however we are unable to run the tests with cargo test, the linker is complaining that some symbols are defined multiple times (__rust_alloc etc.). There was no issue previously when the C/C++ library was not integrating Rust code. There is no issue in Windows, only in Linux.

We made several unsuccessful attempts to fix this issue:

  • Use the lto and strip options for the different profiles
  • Use the cxx create
  • Use the strip linux command line

Using the --allow-multiple-definition linker flag is working, but this looks like a dirty workaround.


Here is a small GitHub project mimicking what we would want to do. Below is an extract of the readme:

Description

This example is composed of several projects in C/Rust that depends on each other in this sequence:
rust_end_user -> rust_wrap -> c_lib -> rust_lib

  • rust_lib:
    • Rust project generating a C style static library
    • Exposes a function rust_add
  • c_lib
    • C static library which is using rust_lib
    • Exposes a function c_add which is calling rust_add
  • rust_wrap
    • Rust wrapper around c_lib
    • Exposes a function wrap_add which is calling c_add
  • rust_end_user
    • An "end user application" that is using rust_wrap as a dependency in the rust project
    • Calls wrap_add

Expected result:

  • rust_lib:
    • Run binary: OK
    • Run unit test: OK
    • Run integration test: OK
    • Run example: OK
  • c_lib
    • Run binary: OK
  • rust_wrap
    • Run binary: OK
    • Run unit test: NOK
      • Link error about several symbols defined multiple times (__rust_alloc, __rust_dealloc, __rust_no_alloc_shim_is_unstable, etc.)
      • If the --allow-multiple-definition is provided to the linker, it works
    • Run integration test: OK
    • Run example: OK
  • rust_end_user
    • Run binary: OK
    • Run unit test: OK
    • Run integration test: OK
    • Run example: OK

And below are the most interesting code extracts:

rust_lib

Cargo.toml

[package]
name = "rust_lib"
version = "0.1.0"
edition = "2021"
publish = false

[lib]
path = "rust_lib.rs"
crate-type = ["staticlib", "lib"]

[...]

rust_lib.rs

#[no_mangle]
pub extern "C" fn rust_add(a: u8, b:u8) -> u8 {
    let r = a + b;
    println!("rust_add: {a} + {b} -> {r}");
    r
}

[...]

rust_lib.h

#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

uint8_t rust_add(uint8_t a, uint8_t b);

#ifdef __cplusplus
}
#endif

c_lib

c_lib.h

#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

uint8_t c_add(uint8_t a, uint8_t b);

#ifdef __cplusplus
}
#endif

c_lib.cpp

#include <stdio.h>

#include "c_lib.h"
#include "../rust_lib/rust_lib.h"

uint8_t c_add(uint8_t a, uint8_t b) {
    uint8_t r = rust_add(a, b);
    printf("c_add: %d + %d -> %d\n", a, b, r);
    return r;
}

compilation

[...]

gcc -c -o $BUILD_DIR/c_lib.o c_lib/c_lib.c
ar rcs $BUILD_DIR/libc_lib.a $BUILD_DIR/c_lib.o

[...]

rust_wrap

rust_wrap.rs

extern "C" {
    fn c_add(a: u8, b: u8) -> u8;
}

pub fn wrap_add(a: u8, b: u8) -> u8 {
    let r = unsafe { c_add(a, b) };
    println!("wrap_add: {a} + {b} -> {r}");
    r
}

[...]

build.rs

use std::process::Command;

fn main() {
    [...]

    println!("cargo::rustc-link-lib=static=c_lib");
    println!("cargo::rustc-link-search=native=../_build/c_lib");

    println!("cargo::rustc-link-lib=static=rust_lib");
    println!("cargo::rustc-link-search=native=../_build/rust_lib/debug");
}

rust_end_user

Cargo.toml

[package]
name = "rust_end_user"
version = "0.1.0"
edition = "2021"
publish = false

[[bin]]
name = "rust_end_user"
path = "main.rs"

[...]

main.rs

use rust_wrap;

fn main() {
    println!("RUST END USER MAIN");
    assert_eq!(rust_wrap::wrap_add(50, 77), 127);
    println!();
}

[...]

The initial tests were made with rust 1.80 (where it seems to indeed be an issue). However it works fine with rust 1.85.

4
  • 1
    First of all, great work on producing a minimal example! Self-contained questions are preferred though -- imagine that a user uses the offline dump and stumbles upon your question, they've got no idea whether your setup matches theirs! -- and therefore it would be great if you could at least reproduce in the question the command lines used to produce the various libraries/binaries, so that all the flags used are plain to see. Commented Feb 26 at 16:24
  • 1
    Whereas we indeed do appreciate a minimal example, it is not just preferred but obligatory that it be presented in the question itself. This ensures that the question remains sensible and properly contextualized independent of anything that may happen to any externally hosted code. Commented Feb 26 at 17:08
  • 1
    probably github.com/rust-lang/rust/issues/73632 You could try to post-process the .a files and keep only the object files of your (sub)project Commented Feb 26 at 17:11
  • added some code extracts in the question. Commented Feb 28 at 8:09

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.