一个包,两个实体:神话还是现实?
\n\n想象一下:你用Rust编写了一个用于处理大数据的出色库。它速度快如闪电,安全且优雅。但您不仅需要供其他开发者使用的API,还需要一个现成的CLI工具,用于测试库或在管道中使用它。这就引出了一个在Stack Overflow上获得数千次浏览的经典问题:“Package with both a library and a binary?”(同时包含库和二进制文件的包)。
\n\n对于Rust新手来说,这听起来像是一个难题。对于经验丰富的开发者来说,这是一个关于优化项目结构和性能的任务。在本文中,我们不仅会回答这个问题,还会重点关注优化和性能——这是Rust最受喜爱的两个特点。
\n\n我们将分析如何正确组织这样的包,避免代码重复,加快编译速度,并使您的二进制文件真正快速。开始吧!
\n\n包结构:Cargo.toml决定一切
\n\n在Rust中,一个Cargo包可以同时包含库(lib)和多个二进制文件(bin)。这不是魔法,而是一个简单的文件夹结构约定。让我们看一个最小的例子。
\n\n创建一个项目:
\n\ncargo new my_tool --lib\n\n现在修改Cargo.toml文件,明确指定我们同时拥有库和二进制文件:
[package]\nname = "my_tool"\nversion = "0.1.0"\nedition = "2021"\n\n[lib]\nname = "my_tool_lib"\npath = "src/lib.rs"\n\n[[bin]]\nname = "my_tool"\npath = "src/main.rs"\n\n[dependencies]\n\n\n文件夹结构将如下所示:
\n\nmy_tool/\n├── Cargo.toml\n├── src/\n│ ├── lib.rs // 库\n│ └── main.rs // 二进制文件\n\n\n很简单,对吧?但魔鬼在于细节。如果您只是将库中的代码复制到main.rs中,您将得到重复代码和性能问题。需要更聪明地操作。
\n\n正确的方法:二进制文件使用库
\n\n二进制文件应该将库作为外部crate导入。即使它们位于同一个包中。这不仅减少了重复,还允许Rust编译器通过跨crate优化(LTO)更好地优化代码。
\n\nsrc/lib.rs示例:
pub fn calculate(data: &[u64]) -> u64 {\n data.iter().sum()\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_calculate() {\n assert_eq!(calculate(&[1, 2, 3]), 6);\n }\n}\n\n\nsrc/main.rs示例:
use my_tool_lib::calculate;\n\nfn main() {\n let numbers = vec![10, 20, 30];\n println!("总和: {}", calculate(&numbers));\n}\n\n\n注意:我们使用my_tool_lib——这是我们在Cargo.toml中为库指定的名称。编译器会自动将它们链接到一个二进制文件中,同时保持模块化。
性能优化:LTO、内联和基准测试
\n\n现在结构已经准备好,我们来谈谈重点——如何从您的包中榨取最大性能。因为如果您正在创建一个数据处理工具,每一微秒都至关重要。
\n\n1. 启用链接时优化(LTO)
\n\nLTO允许编译器跨crate边界优化代码。没有LTO,库和二进制文件中的函数会分别优化。有了LTO,它们会被当作一个整体优化。
\n\n在Cargo.toml中添加:
[profile.release]\nlto = true\ncodegen-units = 1\n\n\n这会增加编译时间,但会使二进制文件明显更快。对于教育网站pythonlib.ru来说,需要强调:LTO是Rust生产构建的标准。
\n\n2. 使用内联提示
\n\n如果库中有关键函数,