首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >一次Rust重写基础软件的实践(二)

一次Rust重写基础软件的实践(二)

作者头像
MikeLoveRust
发布于 2024-01-24 00:49:38
发布于 2024-01-24 00:49:38
29400
代码可运行
举报
运行总次数:0
代码可运行

前言

受到2022年“谷歌使用Rust重写Android系统且所有Rust代码的内存安全漏洞为零” [1] 的启发,最近笔者怀着浓厚的兴趣也顺应Rust 的潮流,尝试着将一款C语言开发的基础软件转化为 Rust 语言。本文的主要目的是通过记录此次转化过程中遇到的比较常见且有意思的问题以及解决此问题的方法与大家一起做相关的技术交流和讨论。

问题描述

本文将记录转化过程中遇到的另外一个问题。该问题是由已经转化完成的 Rust 代码使用到软件中引入的第三方软件包和链接库所导致的。设想这样一个场景:Rust 项目中完成某一个功能点需要用到一个或多个第三方软件包和链接库。这显然是很常见的用户场景,但是由于用户环境不同,用户安装的第三方软件包和链接库的版本不同,使得转化后的 Rust 代码必须要做适当的兼容处理。

这里所说的用户的环境不同,可以理解为芯片指令集的平台不同,如 Intel x86 以及国产的 ARM 麒麟服务器。当然更常见的情形是芯片平台相同,但是存在操作系统层面第三方软件包和链接库安装的差异,如 x86 下的 UbuntuCentOS 中用户安装了不同版本的第三方软件包和链接库等。

事实上,即使排除所有平台和系统层面的差异,由于用户安装了该基础软件所依赖的不同版本的第三方软件包和链接库,然而这些第三方软件包或者链接库由于自身的演进导致不同版本之间存在较大差异(可能实现相同功能的函数和函数签名都有千差万别),这给我重写该软件的工作带来了一些挑战。基于上述说明,在完成重写该基础软件的过程中如何使得转化后的 Rust 代码能兼容该基础软件所依赖的主流第三方软件包和链接库则是我遇到的最大挑战。需要说明的是这里的第三方软件包和链接库可能是基于 Rust 语言开发的,也可能是基于 C 语言开发的。

解决方案

对于此问题的解决方案需要使用 Rust FFI(Foreign Function Interface) [1],这基本上是没有太大争议的。因为在本次软件重写过程中我遇到的场景是:对于不同版本的链接库使用哪个版本的函数取决于用户的安装运行时环境,所以除了 Rust FFI,在代码适配上我还考虑了使用 Rust features [2] 机制。

下面我简化了一下场景和解决方案,同时我把样本代码放到了我的 github [3] 里,欢迎大家一起交流。如样本代码所示,my-rust-bin 文件夹中的一段业务代码需要调用到静态链接库 my_rust_lib 中的函数,该链接库有两个版本 v1(在文件夹 my-rust-lib-v1 中) 和 v2(在文件夹 my-rust-lib-v2 中), 且不同版本的库其函数不一样。

  • my-rust-lib-v1 对应的业务函数为:pub fn my_rust_lib_v1(left: usize, right: usize) -> usize
  • my-rust-lib-v2 对应的业务函数为:pub fn my_rust_lib_v2(left: usize, right: usize) -> usize

另外一个 lib 文件夹的目的其实是为了模拟用户本地安装的链接库。可以分别编译不同版本的静态链接库,然后把生成的库文件(在本例中是)libmy_rust_lib.a, 然后把不同版本的库文件拷贝到此文件夹下,以此来模拟用户环境中安装的不同版本的链接库。解决方案中的关键点在于 my-rust-bin 中,

  • 首先在 my-rust-binCargo.toml 中有定义对应的 features,如下所示:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[features]
v1 = []
v2 = []
  • 其次在 my-rust-binsrc/main.rs 下的代码如下:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#[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);
}

现在我来解读一下这段代码。代码先分别定义一个相同的模块 bindingmylib,然后根据 features 分别引入的依赖,使用的不同的静态链接库函数(my_rust_lib_v1my_rust_lib_v2), 同时通过 compile_error! 定义一个没有设置 v1 和 v2 features 的编译错误(防止编译时忘记设置 features选项,下面在编译环节的时候有用)。最后将两个有差异的函数统一为函数 my_rust_lib,并在该函数中根据 features 定义分别调用不同的函数并返回相应的值。

  • 最后是在 my-rust-bin 中编译二进制文件:

编译并运行 v1 的二进制文件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 编译 v1 版本的 my-rust-bin
$ cd my-rust-bin
$ cargo build --features="v1"

# 运行 v1 版本的 my-rust-bin
$ target/debug/my-rust-bin
my_rust_lib_v1: 8
The return value of my_rust_lib is [8]

编译并运行 v2 的二进制文件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 编译 v2 版本的 my-rust-bin
$ cd my-rust-bin
$ cargo build --features="v2"

# 运行 v2 版本的 my-rust-bin
$ target/debug/my-rust-bin
my_rust_lib_v2: 8
The return value of my_rust_lib is [8]

备注:如果编译的时候没有设置 --features 则会有如下输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ 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");
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

至此,用户在编译好该基础软件之后,就可以无感知的通过统一的函数入口调用不同版本的相同链接库中的不同函数了。

总结

本文主要是在简化了问题的实际场景以后,解决不同版本的同一软件包或者链接库中,函数及其函数签名不同导致的调用问题。之所以说简化,主要是本文所描述的场景中,my-rust-bin 和其依赖的外部链接库均是 Rust 编写。而在我的实际场景中则会更复杂一些,存在着 Rust 代码依赖 C 编写的外部链接库,同时存在混合的原来 C 代码部分依赖新改写的 Rust 外部链接库的情况。但是无论哪种情况,万变不离其宗,我们都可以从这种最简单的场景出发去解决遇到的问题。

关于作者

张怀龙曾就职于阿尔卡特朗讯,百度,IBM等企业从事云计算研发相关的工作。目前就职于 Intel 中国,担任云原生开发工程师并致力于云原生、服务网格等技术领域研究实践,也是Istio 的maintainer的开发者。曾多次在 KubeCon、ServiceMeshCon、IstioCon、GOTC 和 InfoQ/QCon 等大会上发表演讲。

关联博客

一次Rust重写基础软件的实践(一)

参考文档

  • [1] https://doc.rust-lang.org/nomicon/ffi.html
  • [2] https://doc.rust-lang.org/cargo/reference/features.html
  • [3] https://github.com/zhlsunshine/rust-lib-with-multi-versions-example
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-01-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Rust语言学习交流 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Rust FFI 编程 - bindgen 使用示例
当我们拥有一组具有良好声明的头文件时,自己定义 C 库的 Rust FFI 绑定函数是毫无意义的。我们可以使用 bindgen 这种工具从 C 库的头文件生成 Rust FFI 绑定函数。然后,我们运行一些测试代码以验证其是否正常运行,并对它们进行调整,直到正确为止。
MikeLoveRust
2020/09/30
2.2K0
一次Rust重写基础软件的实践(一)
受到2022年“谷歌使用Rust重写Android系统且所有Rust代码的内存安全漏洞为零” [1] 的启发,最近笔者怀着浓厚的兴趣也顺应Rust 的潮流,尝试着将一款C语言开发的基础软件转化为 Rust 语言。本文的主要目的是通过记录此次转化过程中遇到的比较常见且有意思的问题以及解决此问题的方法与大家一起做相关的技术交流和讨论。
MikeLoveRust
2024/01/19
2130
一次Rust重写基础软件的实践(一)
Rust到底值不值得学--Rust对比、特色和理念
其实我一直弄不明白一点,那就是计算机技术的发展,是让这个世界变得简单了,还是变得更复杂了。 当然这只是一个玩笑,可别把这个问题当真。
俺踏月色而来
2019/10/14
2.8K0
Rust的第二次接触-写个小服务器程序
蛮久前入门了一下 Rust 语言。它的设计模型非常地吸引C/C++的开发者。但是学习语言嘛还是要练习一下,之前也用它给我们项目写了个命令行小工具。这回拿来写个小型的服务器程序吧。
owent
2018/10/09
4.3K2
一次Rust重写基础软件的实践(三)
受到2022年“谷歌使用Rust重写Android系统且所有Rust代码的内存安全漏洞为零” [1] 的启发,最近笔者怀着浓厚的兴趣也顺应Rust 的潮流,尝试着将一款C语言开发的基础软件转化为 Rust 语言。本文的主要目的是通过记录此次转化过程中遇到的比较常见且有意思的问题以及解决此问题的方法与大家一起做相关的技术交流和讨论。
MikeLoveRust
2024/01/30
2830
一次Rust重写基础软件的实践(三)
学Rust不学Cargo,等于没学Rust:features特性详解
在 Rust 中,Cargo 的 "features" 是一种机制,允许你在编译你的 crate 时选择不同的配置选项。这样可以在一个 crate 中提供多个功能,并根据需要选择性地启用或禁用这些功能。
程序饲养员
2023/12/30
8300
【Rust日报】2022-05-20 - 用 Rust 扩展 SQLite
作为进程内数据库,SQLite 具有其他扩展机制,例如 用户定义函数(简称 UDF)。但是UDF有一些缺点:
MikeLoveRust
2022/06/10
8200
【Rust日报】2021-03-26 Rust 1.51 稳定版发布!
不过这样可能会导致编译时间加长(因为可能多次编译同一个crate),更详细内容可以看 Cargo Guide 的 "Feature Resolver" 小节。
MikeLoveRust
2021/04/22
7450
Rust crate与模块
假设你正在编写一个程序,用来模拟蕨类植物从单个细胞开始的生长过程。你的程序,也像蕨类植物一样,一开始会非常简单,所有的代码也许都在一个文件中——这只是想法的萌芽。随着成长,它将开始分化出内部结构,不同的部分会有不同的用途。然后它将分化成多个文件,可能遍布在各个目录中。随着时间的推移,它可能会成为整个软件生态系统的重要组成部分。对于任何超出几个数据结构或几百行代码的程序,进行适当的组织都是很有必要的。
草帽lufei
2024/05/08
4050
Rust crate与模块
回撸Rust China Conf 2020 之《浅谈Rust在算法题和竞赛中的应用》
刚刚结束的首届Rust China Conf 2020就是一种交流学习的方式。Rust中文社区采用直播并提供视频回放,为所有Rustacean提供了绝佳的、宝贵的学习资料。
袁承兴
2021/01/04
8070
【Rust】007-包管理与模块管理
首先我们新建一个文件夹my_project,然后为这个文件夹初始化一个 Rust 项目吧!
訾博ZiBo
2025/01/06
2110
【Rust】007-包管理与模块管理
Rust FFI 编程 - Rust导出共享库01
从前面的章节,我们可以看到,C与Rust/Rust与C的交互,核心就是指针的操作。两边的代码使用的是同一个程序栈,栈上的指针能放心地传递,而不用担心被错误释放的问题(栈上内存被调用规则自动管理,C和Rust中都是如此)。两边的代码可能使用不同的堆分配器,因此,堆上的指针的传递需要严格注意,需要各自管理各自的资源,谁创建谁释放。指针传递过程中,需要分析所有权问题。有了这种基本思维模型后,我们用 Rust 进行 FFI 编程,就会心中有数,知道什么时候该做什么,不再是一团浆糊了。
MikeLoveRust
2020/07/21
1.1K0
33.Rust-模块
我们常说 功能模块,就是用于将函数或结构体按照功能分组。也常常把相似的函数或者实现相同功能的或者共同实现一个功能的函数和结构体划分到一个模块中。
面向加薪学习
2022/06/30
2830
编写rust测试程序
当使用 Cargo 创建一个 lib 类型的包时,它会为我们自动生成一个测试模块。先来创建一个 lib 类型的 adder 包。创建成功后,在 src/lib.rs 文件中可以发现如下代码:
zy010101
2023/04/24
1.4K0
rust写操作系统 rCore tutorial 学习笔记:实验指导零 创建项目与启动
这是 os summer of code 2020 项目每日记录的一部分: 每日记录github地址(包含根据实验指导实现的每个阶段的代码):https://github.com/yunwei37/os-summer-of-code-daily
云微
2023/02/11
1.7K0
Rust FFI 编程 - 手动绑定 C 库入门 01
本篇为一个新的章节《手动绑定 C 库入门》的第一篇。从这个章节开始,我们将会进行使用 Rust 对 C 库进行封装的实践。
MikeLoveRust
2020/05/14
1.9K0
Rust workspace的使用
对于较大型项目,随着功能的不断增加,规模的不断扩大,将面临如何组织项目的问题。在这种情况下,可以使用 Cargo workspace来组织和管理项目。
fliter
2024/02/28
4330
Rust workspace的使用
听GPT 讲Rust Cargo源代码(3)
在Rust Cargo的源代码中,cargo/src/bin/cargo/commands/fix.rs文件的作用是实现了cargo fix命令。cargo fix命令用于自动修复源代码中的一些错误和不规范的代码风格,以提高代码的可读性、可维护性和性能。
fliter
2024/04/25
2500
听GPT 讲Rust Cargo源代码(3)
react+rust+webAssembly(wasm)示例
前言:WebAssembly(简称wasm)已经出来有几年了,在一些需要高性能的web应用场景中,wasm技术可以让代码执行效率大大提升。react做为目前大厂主流的前端框架之一,搭配上最近几年一直越来越火的Rust语言,可以很好的结合起来,形成wasm的解决方案。国外有高人给出了一篇详细的英文入门教程(见本文最后的参考文章链接),下面是主要使用步骤。
菩提树下的杨过
2022/08/23
1.7K0
react+rust+webAssembly(wasm)示例
【Rust日报】2022-01-02 - Rust在嵌入式大有可为
链接:https://blog.rust-embedded.org/this-year-in-embedded-rust-2021/
MikeLoveRust
2022/01/21
1.2K0
相关推荐
Rust FFI 编程 - bindgen 使用示例
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验