Skip to content

Rust的依赖问题

Rust的优点

Rust有两个非常吸引人的优点:很容易与c/c++库集成;非常好用的包管理。

包管理让Rust相比c/c++的开发效率高了很多。c/c++因为没有包管理,所以代码复用比较困难,大家都倾向于将代码直接拷贝修改之后放在自己工程里面,甚至是仅仅参考思路,代码重写。

Rust作为一门新的语言,其仓库中的包还不够丰富。但是通过封装成熟的c/c++库,很快就建立了够用的生态。

静态编译

Rust倾向于静态编译,一个项目编译的时候,会将其依赖的包的源码下载到本地,然后编译。Rust的模块化管理也做的相当好,项目本身的代码与依赖的包通过模块进行互动。

源码分发相对来说比二进制分发要简单一些,因为二进制分发需要有成熟稳定的ABI。这方面Java做的很好,因为它可以通过jvm屏蔽平台差异,提供一个稳定的ABI。

但是Rust的静态编译并没有像Go那么彻底。Go为了支持并发,自己做了runtime,不依赖libc。而Rust还是依赖系统的libc的。当然Go也可以通过CGO直接调用c/c++动态库。

依赖问题

包管理太好用带来的一个问题就是引入依赖太容易了,只要在Cargo.toml文件里面加一行依赖就可以直接用了。cita现在所有的依赖包已经将近400个了,对系统动态库的依赖也有十多个了。现在的形势已经逐渐向npm演化了。

Rust默认是在每个crate下面编译各自的依赖。但是对于cita这样的大工程来说,重复编译会比较多,编译时间和硬盘占用都会增大。编译生成的中间文件也会分散在各个子目录,导致gitignore比较难处理。因此我们使用了workspace技术,编译时会将所有的中间文件都放在顶层目录里,同样的依赖包也只会编译一次。

但是问题就是依赖冲突的概率大大增加了。比如,这个例子,secp256k1和git-sys对cc这个依赖的版本要求是冲突的。当然这里是一个最小化的复现,如果这两个包是在不同的crate里面,它们是可以不相互干扰的。但是使用workspace之后,即使它们在不同的crate,只要在一个大工程里面,就会产生冲突。更糟糕的是前面issue里提到的问题,rust的报错信息不够准确,本来是secp256k1和git-sys的冲突,报错信息确实secp256k1和backtrace冲突。当依赖多的时候,碰到这类依赖冲突问题,基本无法解决。