A practice of rewriting foundation software from C to Rust(2)

Practice of C to Rust

Huailong Zhang
Rustaceans
5 min readJan 22, 2024

--

Rust Mascot

Preface

Inspired by “Google use Rust to rewrite the Android system and all Rust codes have zero memory security vulnerabilities” [1] in 2022, and with a strong interest in following the trend of Rust, I am trying to convert a foundation software from C into Rust.

The primary purpose of this article is to communicate with everyone on the usage of Rust by recording these common and interesting problems I encountered during the conversion and how to solve these problems.

Problem Description

This blog document another problem encountered during the conversion process: the Rust code converted from C is using the static or dynamic libraries for this software.

Let’s consider the scenario: I need to utilize a series of static or dynamic library to complete a certain functionality. But due to variations in user environments and different versions of installed static or dynamic libraries, the converted Rust code must undergo appropriate compatibility handling.

Diverse user environments mentioned above can be understood as different chip instruction set platforms, such as Intel x86 and Kirin ARM servers. And the more common scenario involves the same chip platform but differences in the installation of third-party software packages and linking libraries in the operating system level, such as users may install different versions of a third-party software packages or linking libraries in Ubuntu and CentOS under Intel x86.

In fact, even eliminating all platform differences, there can be significant disparities between versions of the same library or package due to their evolution. These differences may include varying function implementations and function signatures, even though they serve the same purpose.

In light of the above explanations, addressing how to make the converted Rust code compatible with the mainstream third-party software packages and linking libraries which the foundation software is relied on is the most significant challenge I have encountered in the conversion process. It’s worth noting that these third-party software packages and linking libraries might be developed in either the C or Rust programming languages.

Solution

The solution to this problem requires using Rust FFI (Foreign Function Interface) [2], which is essentially undisputed. In addition to Rust FFI, I also consider using Rust features [3] mechanisms for code adaptation because I encountered a scenario in which version of functions to use from different versions of a linking library depends on the user’s runtime environment.

I have simplified the scenario and solution as below and also placed the sample code on my github[4], and welcome everyone to discuss it together. As shown in the sample code, a segment of business code in the my-rust-bin folder needs to call functions from the statically linking library my_rust_lib. This library has two versions, v1 (in the my-rust-lib-v1 folder) and v2 (in the my-rust-lib-v2 folder). Additionally, the functions in different versions of the library are different:

The function corresponding to my-rust-lib-v1 is: pub fn my_rust_lib_v1(left: usize, right: usize) -> usize

The function corresponding to my-rust-lib-v2 is: pub fn my_rust_lib_v2(left: usize, right: usize) -> usize

The purpose of another lib folder is actually to simulate locally installed linking libraries for the user. Different versions of static linking libraries can be compiled, and the generated library files (in this case, libmy_rust_lib.a) can be copied to this folder. This is done to simulate the installation of various versions of linking libraries in the user’s environment. The key point in the solution lies within the my-rust-bin component.

  • Firstly, in the Cargo.toml file of my-rust-bin, features are defined as follows:

[features]
v1 = []
v2 = []
  • Next, the code in src/main.rs of my-rust-bin is as follows:
#[cfg(feature = "v1")]
mod bindingmylib {
extern "C" {
pub fn my_rust_lib_v1(left: usize, right: usize) -> usize;
}
}

#[cfg(feature = "v2")]
mod bindingmylib {
extern "C" {
pub fn my_rust_lib_v2(left: usize, right: usize) -> usize;
}
}

#[cfg(not(any(feature = "v1", feature = "v2")))]
compile_error!("Please specify either 'v1' or 'v2' feature");

pub fn my_rust_lib(left: usize, right: usize) -> usize {
#[cfg(feature = "v1")]
unsafe {
return bindingmylib::my_rust_lib_v1(left, right);
}

#[cfg(feature = "v2")]
unsafe {
return bindingmylib::my_rust_lib_v2(left, right);
}
}

fn main() {
let r_value: usize = my_rust_lib(3, 5);
println!("The return value of my_rust_lib is [{}]", r_value);
}

Let me interpret this snippet of code. The code firstly defines a module named bindingmylib. Then, based on the features, it introduces dependencies using different static linking library functions (my_rust_lib_v1 and my_rust_lib_v2). Additionally, it uses compile_error! to define a compilation error when v1 and v2 features are not set (to prevent forgetting to set the features during compilation, which will be useful in the compilation phase below). Finally, it consolidates the two different functions into a single function called my_rust_lib. In this function, it calls different functions based on the defined features and returns the respective values.

- Finally, complete the compilation of the binary file in my-rust-bin:

Compile and run the binary file for version 1

# Compile version 1 of my-rust-bin
$ cd my-rust-bin
$ cargo build - features="v1"

# Run the v1 version of my-rust-bin
$ target/debug/my-rust-bin
my_rust_lib_v1: 8
The return value of my_rust_lib is [8]

Compile and run the binary file for version 2

# Compile version 2 of my-rust-bin
$ cd my-rust-bin
$ cargo build - features="v2"

# Run the v2 version of my-rust-bin
$ target/debug/my-rust-bin
my_rust_lib_v2: 8
The return value of my_rust_lib is [8]

Note: If the — features flag is not set during compilation, the following output occur:

$ cargo build
error: Please specify either 'v1' or 'v2' feature
→ src/main.rs:16:1
|
16 | compile_error!("Please specify either 'v1' or 'v2' feature");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

At this point, users, after compiling the foundation software, can seamlessly call different functions from the same linking libraries through a unified function entry point without being aware of the differences in versions.

Conclusion

After simplifying the real-world scenarios, the main focus of this blog is to address problem arising from the differences in function and its signatures in various versions of the same software package or linking library.

The term “simplify” is used because, in above described scenario, both my-rust-bin and the linking libraries it depends on are all written in Rust. In my actual scenario, things are a bit more complex, involving Rust code depending on linking libraries written in C, and the coexistence of originally C code parts relying on newly rewritten Rust linking libraries.

However, regardless of the specific scenario, the essence remains unchanged, and we can start by solving the encountered problems from this simplest case.

About Author

Huailong Zhang (Steve Zhang) has worked for Alcatel-Lucent, Baidu and IBM to engage in cloud computing R&D, including PaaS and DevOps platform development. He is working in Intel SATG now, focusing on cloud native ecosystem, such as kubernetes and service mesh. He is also an Istio maintainer and has been a speaker at KubeCon, ServiceMeshCon, IstioCon, InfoQ/QCon and GOTC etc.

Related blogs

A practice of rewriting basic software from C to Rust(1)

References

[1] https://security.googleblog.com/2022/12/memory-safe-languages-in-android-13.html
[2] https://doc.rust-lang.org/nomicon/ffi.html
[3] https://doc.rust-lang.org/cargo/reference/features.html
[4] https://github.com/zhlsunshine/rust-lib-with-multi-versions-example

Rustaceans 🚀

Thank you for being a part of the Rustaceans community! Before you go:

  • Show your appreciation with a clap and follow the publication
  • Discover how you can contribute your own insights to Rustaceans
  • Connect with us: X | Rust Bytes Newsletter

--

--

Huailong Zhang
Rustaceans

I am working on Intel SATG as a cloud software engineer and is Istio maintainer and been a speaker at KubeCon, ServiceMeshCon, IstioCon, QCon and GOTC