我是 Rust 新手,刚刚读完这本书。我试图了解 Rust 中的所有内容是否都必须以源代码的形式分发,就像 golang 一样。这可能不是准确的提问方式,但这是我想知道的:
在 C/C++ 时代,我可以将代码编译到某个库(xx.a 或 xx.so)中,并将其与头文件一起分发给可以链接其代码的人,只要我们使用相同的编译器/操作系统/平台。现在,Rust 的每个依赖项都以源代码的形式从 crates.io 中提取,并在本地构建和链接。但是,如果由于某种原因我不想发布源代码而只想发布目标平台的库,可以通过操作工具链来完成此操作吗?我相信由于 Rust 的一些固有设计,这是不可能做到的。例如,我可以在 Rust 库中编写这样的代码:
pub trait Summary {
// snip
}
fn returns_summarizable() -> impl Summary {
// snip
}
如果依赖于库的代码调用
returns_summarizable
,则编译器必须能够确定返回值的大小,以便在堆栈上为结果变量分配空间。这意味着虽然代码只关心它返回某种实现 Summary
特征的类型,但编译器实际上必须知道它返回的具体类型。如果没有库的源代码,就无法完成此操作,甚至无法使用头文件等类似的东西,因为头文件(如果存在)应该只包含特征声明,而不包含函数可能返回的任何内部类型。
我对此感到好奇,不是因为我想在没有源的情况下分发包,而是因为我想到借用检查器和编译器从不查看调用它的函数或方法的主体,而只查看签名。当函数返回
impl xxx
时,情况似乎并非如此,因为 Rust 必须查看实际返回的实现类型来确定返回类型大小,对吗?
这里的主要问题是 Rust ABI 还不稳定。 因此,很难正确分发“Rust 二进制文件”,因为它们只能与您编译它们的完全相同的编译器一起使用。即使是较小的版本更改也可能会破坏它们。
目前安全的选择是通过在
staticlib
中设置板条箱类型来分发
cdylib
和/或
Cargo.toml
[lib]
crate-type = ["staticlib", "cdylib"]
将分别生成静态库文件
*.a
和动态库文件*.so
/*.dylib
/*.dll
,请参阅链接参考文章。
缺点是,这使用了 FFI 接口,因此你失去了一些 Rust 的表达能力,只能使用 C 兼容类型,这就排除了泛型之类的东西。
如果您不介意破坏/重建每个 Rust 小版本,您可以使用 Rust
dylib
:
[lib]
crate-type = ["dylib"]
这将在 Linux 上生成
lib<your_crate_name>.so
。
然后,您可以告诉 Cargo 使用构建脚本或类似脚本链接该文件:
fn main() {
println!("cargo:rustc-link-lib=dy_lib");
println!("cargo:rustc-link-search=/home/jona/projects/dy-lib/target/release");
}
您还必须在 extern 块中直接使用它或在存根库中提供函数签名:
extern "Rust" {
fn your_exported_function(Vec<i8>, String) -> Result<(), &'static str>;
}
请注意,由于泛型是通过单态实现的,所以仍然不允许它们,但你至少可以使用 Rust 表达类型系统的其余部分,尽管
extern
块中的所有函数都是隐式的 unsafe
。
虽然你可以返回
impl Summary
,但似乎你无论如何都不能动态返回两种不同的类型,例如这段代码无法编译。 游乐场:
use rand;
trait Summary {
fn get(&self) -> usize;
}
struct A(usize);
impl Summary for A {
fn get(&self) -> usize { return self.0; }
}
struct B(usize);
impl Summary for B {
fn get(&self) -> usize { return self.0; }
}
fn returns_summarizable() -> impl Summary {
if rand::random() {
return A(42);
} else {
return B(21);
}
}
fn main() {
println!("{}", returns_summarizable().get());
}
error[E0308]: mismatched types
--> src/main.rs:20:16
|
16 | fn returns_summarizable() -> impl Summary {
| ------------ expected `A` because of return type
...
20 | return B(21);
| ^^^^^ expected struct `A`, found struct `B`
不过,如果两个分支都返回类型
A
,那么代码就会随机打印 42
或 21
。因此只要它只返回一种类型,编译器就可以将二进制签名中的impl Summary
替换为A
的大小