资讯详情

Rust cdylib 详解:为 C/C++ 导出动态库的 crate-type 设计(RFC 1510)

📅 2026/10/15 2:06:28 | 华诺云谱 👁 阅读
Rust cdylib 详解:为 C/C++ 导出动态库的 crate-type 设计(RFC 1510)
文档【免费下载链接】rfcsRFCs for changes to Rust项目地址https://gitcode.com/gh_mirrors/rf/rfcs点击查看免费下载导读cdylib是 rustc 提供的一种专用 crate-type它让 Rust 代码可以被编译成面向 C 接口导出的动态共享库.so / .dylib / .dll并被 C、C、Python、Java、.NET 等其他语言或宿主程序动态加载。本篇文章以 RFC 1510text/1510-cdylib.md 为核心结合本仓库中与之相关的后续 RFC系统梳理cdylib的诞生动机、与dylibrdylib的本质差异、元数据与符号可见性规则、LTO 与链接策略以及在 Cargo 生态中的完整实战用法。读完本文你将掌握如何在Cargo.toml、#![crate_type]与rustc --crate-type之间正确选择构建方式并理解符号导出、FFI 导出、链接策略背后的编译器原理。一、背景Rust 的两种动态库形态RFC 1510 指出在引入cdylib之前rustc 支持两种动态库生成模式rdylibRust 动态库面向Rust 与 Rust 之间的复用场景。这种库会暴露所有 Rust 符号并动态链接标准库等依赖主要用于编译器自身实现插件等场景。cdylibC 动态库面向把 Rust 应用嵌入其他语言/应用的场景。它与 rdylib 的主要区别是更倾向于静态链接其他 Rust 库把它们打包进自身最终对外导出 C API。但当时的功能平衡过度偏向使用面极小的 rdylib。由于 Rust 默认静态链接且 ABI 不稳定rdylib 实际使用很少却给 cdylib 使用场景强加了许多不必要的约束元数据Metadata所有动态库都包含 Rust 编译元数据段但把 Rust 加载到别处时根本用不到它符号可见性动态库暴露的是可达reachable符号而把 Rust 加载到别处时和可执行文件一样只需要导出公共的、非 Rust-ABI 的函数。过度暴露会导致库体积不必要地膨胀也会错失函数实际上是私有的这一优化机会LTO无法为 dylib 运行 LTO因为 dylib 面向最终产品而非中间产物。RFC 1510 的动机正是通过引入一种新的 crate-type 来解决这些缺点——即今天的cdylib。二、核心设计cdylib与dylib的四大差异RFC 1510 规定编译器新增接受cdylib这一 crate type可通过两种方式启用rustc --crate-type cdylib或通过 crate 属性#![crate_type cdylib]需要注意的是两种 crate-type 产出的字面工件文件、文件名等完全相同——区别在于内部结构。RFC 明确列出四大差异维度dylibrdylibcdylib元数据库中包含 Rust 编译元数据段不包含元数据段符号可见性像 rlib 一样暴露全部符号像可执行文件一样暴露符号LTO不允许允许链接方式默认动态链接其他 Rust 库如标准库默认静态链接所有 Rust 依赖其中符号可见性规则尤为关键pub fn foo() {}不会被导出而#[no_mangle] pub extern fn foo() {}才会被导出。同时编译器还可以向链接器传递额外标志主动隐藏来自被链接库的 Rust 导出符号。三、符号导出规则#[no_mangle]与extern C在cdylib中能被外部看见的只有两类符号#[no_mangle] pub extern C fn add(a: i32, b: i32) - i32 { a b }这里有两个关键点extern C声明函数使用 C ABI这是与 C/C 互操作的前提#[no_mangle]关闭 Rust 名称修饰name mangling使最终符号名与函数名一致外部才能按名字查到。普通的pub fn即使带pub在cdylib中也不会被导出——这正是 RFC 1510 与可执行文件行为对齐的体现。符号可见性相关的后续演进可以参看本仓库中的 RFC 3834text/3834-export-visibility.md它提出用#[export_visibility target_default]覆盖#[no_mangle]的默认公开导出行为并可用-Zdefault-visibilityhidden配合隐藏符号从而减小二进制体积、加快 DSO 加载并避免符号名碰撞引发的未定义行为。该 RFC 指出#[no_mangle]/#[export_name]符号在rustc中会被强制设置为最大可见性对应rustc_monomorphize/src/partitioning.rs中的符号导出逻辑这正是 cdylib 场景下控制导出面时的核心机制。四、缺点与备选方案RFC 1510 原文RFC 1510 坦诚地记录了自身局限与备选设计缺点Rust 的链接模型本身临时且定义不清本 RFC 是对该模型的扩展可能在与该输出格式的使用场景之间出现不可预见的交互。备选方案最初提议新增的 crate-type 是rdylib并把现有dylib重新解释为 cdylib 使用场景。但这属于破坏性变更需要在 Cargo 中为插件准备相当复杂的过渡计划最终认为不值得为cdylib 可能正是你想要的这一收益而付出代价。未决问题现有dylib格式是否应被视为不稳定是否要求 nightly 编译器由于 Rust 动态库的使用场景极其有限且易变RFC 建议默认对其访问加以门控。五、实战一通过 Cargo.toml 构建 cdylib在实际工程中最常用的方式是修改Cargo.toml的[lib]段[lib] name myffi crate-type [cdylib]crate-type 的取值在 Cargo 文档中与本 RFC 一致常用的有lib默认生成 rlibRust 库cdylibC 兼容动态库staticlibC 兼容静态库.a/.libdylibRust 动态库rdylib。关于[lib]目标配置的完整字段如path、test、doctest、crate-type等本仓库的 RFC 3772text/3772-build-target-edition.md 有完整的清单引用。构建后产物命名规则为libname.soLinux、libname.dylibmacOS、name.dllWindows。一个库同时产出多种格式一个 crate 可以同时声明多个 crate-type[lib] crate-type [lib, staticlib, cdylib]这样既可供 Rust 依赖方使用 rlib又可供 C 侧分别选择静态或动态链接。RFC 1510 提到同一仓库中 RFC 3028text/3028-cargo-binary-dependencies.md 的artifact依赖机制正是围绕cdylib/staticlib/bin三类产物展开的Cargo 允许其他 crate 以artifact cdylib的形式依赖某个包产出的动态库并在构建期通过CARGO_CDYLIB_FILE_DEP环境变量拿到产物完整路径。例如[dependencies] mypreload { version 1.2.3, artifact cdylib }// main.rs const MY_PRELOAD_LIB: [u8] include_bytes!(env!(CARGO_CDYLIB_FILE_MYPRELOAD));六、实战二命令行方式指定 crate-type如果不希望改动Cargo.toml可以直接用 rustcrustc --crate-type cdylib src/lib.rs -o libfoo.so也可以借助 Cargo 的cargo rustc在命令行覆盖 manifest 中的设置——这正是 RFC 3180text/3180-cargo-cli-crate-type.md 引入的能力cargo rustc --crate-type cdylib --features ffi其规则为--crate-type取值与 manifest 中允许的值一致可传逗号分隔的多个值仅对构建lib或example生效若同时提供了命令行参数与 manifest 中的crate-type列表命令行参数优先覆盖而非追加。该 RFC 的动机说明与cdylib场景直接相关有些 crate如 hyper同时提供 Rust API 与可选 C API多数 Rust 用户只需要rlib强制编译cdylib属于浪费跨平台共享库时iOS 需要staticlibAndroid 与 WASM 需要cdylib而在 Rust 中作为依赖又需要rlib命令行覆盖能显著缩短构建时间。七、实战三Windows 下导出序号与符号控制在 Windows 上构建cdylib即 DLL时符号导出行为与 ELF/Mach-O 平台不同。RFC 3641text/3641-export-function-ordinals.md 专门处理了按序号导出的需求某些应用如 Microsoft Detours要求特定函数必须是固定 Ordinal如DetourFinishHelperProcess必须为 Ordinal 1。在 RFC 3641 之前只能借助.def文件与build.rs; lib.def LIBRARY EXPORTS DetourFinishHelperProcess 1// build.rs pub fn main() { let lib_def path/to/lib.def; println!(cargo:rustc-cdylib-link-arg/DEF:{}, lib_def); }其最大缺点是一旦指定.def文件就必须为 DLL 中每个要导出的函数都指定序号否则它们不会出现在生成的.lib中。RFC 3641 提出用#[unsafe(export_ordinal(n))]属性直接在源码中标注#[no_mangle] #[unsafe(export_ordinal(1))] pub fn hello() {} // 也支持 extern / unsafe 函数 #[no_mangle] #[unsafe(export_ordinal(2))] pub unsafe extern C fn world() {}序号n必须是 1的正整数且在整个程序中唯一重复会被报错该属性只影响 Windows 目标未标注时编译器会在编译期分配一个未使用的序号。这与link_ordinal属性按序号导入形成对称。八、与#[used]、全局分配器等其他机制的配合cdylib 与其他编译器机制常有交集仓库中有多处佐证RFC 2386text/2386-used.md 在解释#[used]时明确以cdylib作为共享库产物的示例rustc 先把 crate 编译成目标文件再调用链接器生成可执行文件或共享库当 crate type 为cdylib时即.so。#[used]只保证静态变量保留在目标文件中并不影响链接器行为。RFC 1974text/1974-global-allocators.md 提到在cdylib/staticlibcrate 中选择全局分配器应当是该产物唯一需要关注的分配器选择点——这类 C 兼容产物通常会被嵌入其他语言运行时分配器选择直接影响跨语言内存安全。九、常见问题与设计权衡回顾为什么pub fn在 cdylib 中默认不被导出这与可执行文件的行为一致只有显式以extern C#[no_mangle]标注的符号才是 FFI 边界。这种设计让编译器能进行更多优化把函数当作私有处理也让库的导出面最小化。若需要导出可执行文件而非动态库中的符号本仓库的 RFC 2841text/2841-export-executable-symbols.md 提出通过-C export-executable-symbols让可执行文件像动态库一样导出符号服务于宿主程序内嵌 JVM/CLR 等运行时并让它们回调 Rust的场景。LTO 为什么对 cdylib 开放因为 cdylib 是最终产物被外部加载的成品库可以安全地进行跨 crate 的 LTO而 rdylib 是中间产物还要参与后续链接不允许 LTO。这也是两者在编译器优化管线中的根本区别之一。如何进一步控制导出符号的可见性在 nightly 上可以结合-Zdefault-visibilityhidden与#[export_visibility target_default]见 RFC 3834把默认公开导出的#[no_mangle]符号改为目标平台默认可见性从而缩小二进制体积避免跨语言 LTO 无法消除的重复副本与死代码加快 DSO 加载缩小动态符号表防止外部代码依赖内部实现细节减少符号名碰撞导致的未定义行为详见该 RFC 中对 Chromium 实际事故的案例分析。十、总结RFC 1510 以一次小而精准的编译器扩展将Rust 动态库与C 接口动态库两种完全不同的使用形态彻底区分开cdylib无元数据、按可执行文件的规则导出符号、允许 LTO、默认静态链接 Rust 依赖因而成为 Rust 跨语言 FFI 动态库的标准构建方式。围绕它Cargo 生态逐步补齐了cargo rustc --crate-typeRFC 3180、artifact 依赖RFC 3028、Windows 导出序号RFC 3641、导出可见性控制RFC 3834等配套能力。对于任何需要把 Rust 编译成 .so/.dylib/.dll 供 C/C 或其他语言加载的开发者理解本文所述的符号导出规则与链接策略是写出正确、精简、可维护 FFI 库的前提。延伸阅读仓库内相关文档RFC 1510 原文cdylibRFC 3180cargo rustc --crate-type 命令行参数RFC 3028Cargo 对 bin/cdylib/staticlib 的 artifact 依赖RFC 3641Windows cdylib 导出序号属性RFC 3834export_visibility 符号可见性控制RFC 2841可执行文件导出符号RFC 2386#[used] 与 cdylib 编译流程RFC 1974全局分配器与 cdylib/staticlib赞分享文档【免费下载链接】rfcsRFCs for changes to Rust项目地址https://gitcode.com/gh_mirrors/rf/rfcs点击查看免费下载相关推荐Rust 宏系统改革RFC 0453$crate、macro_use 与宏的导入导出机制Rust 宏系统改革RFC 0453$crate、macro_use 与宏的导入导出机制 output_article Rust 宏系统改革RFC 0文档Rust RFC 558 详解禁止链式比较运算符为何 Rust 要求为 a b c 加括号Rust RFC 558 详解禁止链式比较运算符为何 Rust 要求为 a b c 加括号 本篇基于 Rust 语言 RFC 流程文档库rfc文档RT-Thread 编写 Rust 动态库cdylib模块实战指南RT Thread 编写 Rust 动态库cdylib模块实战指南 RT Thread 不仅支持用 C 语言编写内核与应用还在 components/ru操作系统嵌入式物联网嵌入式OSRTOS上一篇PI0Fast-libero-v044配置文件详解定制输入特征与动作输出的关键参数下一篇终极安全指南Virtual-Display-Driver用户模式驱动的核心优势创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑