文档【免费下载链接】rfcsRFCs for changes to Rust项目地址https://gitcode.com/gh_mirrors/rf/rfcs点击查看免费下载本文基于 text/3424-cargo-script.mdRust 官方 RFC 仓库中的 eRFC 3424展开系统讲解 Cargo 如何以单文件包single-file package形式支持像脚本一样直接运行.rs文件文件内嵌 TOML 格式的 Cargo manifestcargo file.rs作为cargo run --manifest-path file.rs的快捷方式并支持把cargo写进#!行实现 Unix 式脚本执行。读完本文你将掌握该特性的动机、使用方式、设计原则与悬而未决的问题并了解它在仓库内后续 RFCtext/3502-cargo-script.md、text/3503-frontmatter.md中的演进脉络。一、这是什么实验性 RFC 与单文件包RFC 3424 是一份eRFCexperimental RFC。在 Rust 的 RFC 流程中参见 README.md 对 RFC 生命周期的描述常规 RFC 直接确定设计而 eRFC 只确立我们要做什么的意图许多实现细节被有意留给实际实现去探索、验证再收集社区反馈。本文档开头明确写道Thisexperimental RFCadds unstable support for single-file packages in cargo so we can explore the design and resolve questions with an implementation to collect feedback on.它的核心提案是两项单文件包一个.rs文件内部嵌入一份 manifest清单。它被 Cargo 接受的方式与Cargo.toml通过--manifest-path被接受的方式完全相同。命令快捷方式修改cargo使其接受cargo file.rs作为cargo run --manifest-path file.rs的简写从而允许把cargo直接放进#!行shebang来运行这些文件。一个完整的示例RFC 给出的示例是一个带clap依赖、解析命令行参数的脚本#!/usr/bin/env cargo //! cargo //! [dependencies] //! clap { version 4.2, features [derive] } //! use clap::Parser; #[derive(Parser, Debug)] #[clap(version)] struct Args { #[clap(short, long, help Path to config)] config: Optionstd::path::PathBuf, } fn main() { let args Args::parse(); println!({:?}, args); }执行效果注意 eRFC 阶段这里仍以 doc-comment 代码块承载 manifest$ ./prog --config file.toml Args { config: Some(file.toml) }把 manifest 放在文件开头的模块级 doc-comment//!中用带cargo语言标记的围栏代码块fenced code block包裹是 eRFC 3424 阶段提出的嵌入方式。需要说明的是这一具体语法在后来的设计演进中发生了变化——仓库内的 text/3502-cargo-script.md正式 RFC与 text/3503-frontmatter.mdfrontmatter 语法 RFC将其替换为文件顶部的---分隔 frontmatter 语法。后文会有专节对比。二、动机为什么需要单文件包RFC 从四个维度论证了必要性这些论据也是后续正式 RFC 一直沿用的核心。协作Collaboration分享 bug 复现用例时一切都存在于一段可复制粘贴的代码里是最方便的。现实情况是人们分享复现片段时要么干脆省略 manifest要么把 manifest 的细节写得含混不清。同样的道理也适用于给同事、在书或博客中分享教学用代码样例。互操作Interoperability评估一个提案是否值得纳入一个角度是看它是否存在单一明显解。单文件包虽然谈不上有一个唯一答案但存在足够大的一致子集。把这一子集标准化后不同解决方案之间就能更好地互操作——例如 Rust Playground 将来也可能直接支持这种语法从而让协作更顺畅。原型Prototyping想快速尝试某个 API 或语言特性当前只有两条路各有痛点用 Playground无法访问本地资源受限于支持的 crate 集合。手动cargo new再编辑Cargo.toml与main.rscargo run最后删除步骤太多摩擦太大如果创建的位置被 Cargo 判定为某个 workspace 的成员还会直接失败。有了单文件包实验的建立与拆除都更简单人们更愿意去做所有 crate 都可用本地资源可访问。一次性工具One-Off Utilities在目录里放一堆单文件 bash 或 python 脚本并加入 PATH 是轻而易举的事。而 Rust 这边每个脚本都要cargo new一个独立目录还要为每个脚本写包装器、在包装器里传--manifest-path给cargo run才能放进 PATH。三、上手指南Guide-level explanation作为 eRFC这一部分意在传达目标体验细节在稳定前可能变动。创建第一个单文件包仿照 Cargo Book 的创建新项目章节新建一个名为hello_world.rs的文件#!/usr/bin/env cargo fn main() { println!(Hello, world!); }运行它$ chmod x hello_world.rs $ ./hello_world.rs Hello, world!注意这里完全不需要manifest——嵌入的 manifest 是可选的这也是后续设计的一条重要原则降低起步摩擦。添加依赖[crates.io] 是 Rust 社区的中央包注册表cargo默认配置为从它查找并下载依赖。要让hello_world.rs依赖 crates.io 上的库修改文件为#!/usr/bin/env cargo //! cargo //! [dependencies] //! regex 1.8.0 //! fn main() { let re Regex::new(r^\d{4}-\d{2}-\d{2}$).unwrap(); println!(Did our date match? {}, re.is_match(2014-01-01)); }这里的关键机制doc-comment 中的cargo代码块称为manifest包含 Cargo 编译该包所需的全部元数据使用TOML格式书写任意模块内 doc-comment 风格//!都被支持regex 1.8.0中regex是 crate 名1.8.0是 SemVer 版本需求表达式关于可用的版本需求写法Cargo Reference 的指定依赖一章有更详细说明。重新运行后Cargo 会拉取新依赖及其所有传递依赖。用--verbose可以看到完整过程eRFC 中的输出示例注意其中的版本号是示意性的历史版本$ cargo --verbose ./hello_world.rs Updating crates.io index Downloading memchr v0.1.5 Downloading libc v0.1.10 Downloading regex-syntax v0.2.1 Downloading memchr v0.1.5 Downloading aho-corasick v0.3.0 Downloading regex v0.1.41 Compiling memchr v0.1.5 Compiling libc v0.1.10 Compiling regex-syntax v0.2.1 Compiling memchr v0.1.5 Compiling aho-corasick v0.3.0 Compiling regex v0.1.41 Compiling hello_world v0.1.0 (file:///path/to/package/hello_world) Did our date match? trueCargo 会把所用各依赖的精确版本信息缓存在CARGO_HOME中。此后即使regex发布了新版本构建仍会沿用已缓存的版本直到你主动执行$ cargo update --manifest-path hello_world.rs注意eRFC 阶段的所有命令都通过--manifest-path指向.rs文件本身。四、单文件不够时的包布局当单文件承载不下时可以退回到传统的多文件包Cargo.toml加src/main.rs。创建方式依然是$ cargo new hello_world --bin--bin表示二进制程序库则用--lib。默认还会初始化 git 仓库不需要时可加--vcs none。生成的结构$ cd hello_world $ tree . . ├── Cargo.toml └── src └── main.rs 1 directory, 2 files与单文件包不同这里的Cargo.toml需要更多上下文[package] name hello_world version 0.1.0 edition 2021 [dependencies]Cargo 依赖目录约定的完整布局如下便于快速上手新包. ├── Cargo.lock ├── Cargo.toml ├── src/ │ ├── lib.rs │ ├── main.rs │ └── bin/ │ ├── named-executable.rs │ ├── another-executable.rs │ └── multi-file-executable/ │ ├── main.rs │ └── some_module.rs ├── benches/ │ ├── large-input.rs │ └── multi-file-bench/ │ ├── main.rs │ └── bench_module.rs ├── examples/ │ ├── simple.rs │ └── multi-file-example/ │ ├── main.rs │ └── ex_module.rs └── tests/ ├── some-integration-tests.rs └── multi-file-test/ ├── main.rs └── test_module.rs要点Cargo.toml与Cargo.lock位于包根目录源码放在src目录默认库文件是src/lib.rs默认可执行文件是src/main.rs其他可执行文件放src/bin/benchmarks 放benchesexamples 放examples集成测试放tests若某个 binary/example/bench/test 由多个源文件组成则在对应目录的子目录中放一个main.rs连同其余模块可执行文件以目录名命名。这部分布局约定与单文件包形成对照单文件包开箱即用多文件包则遵循 Cargo 的传统约定两者之间应能平滑迁移。五、参考级解释与设计指南Reference-level explanationeRFC 3424 明确把实现细节推迟给实现阶段只给出评估决策时的初始指导方针共三条。指南一单文件包应有一流体验first-class experience不能让它看起来像黑客手段或临时拼凑可迁移的知识经验、StackOverflow 答案等在单文件与多文件包之间用户应能在无辅助的情况下迁移工作流偏离越多cargo 团队的维护与支持成本就越高。由此推导出的具体含义包括运行测试等工作流应与多文件包保持一致而不是被割裂成两套manifest 格式应与多文件包相同而不是使用专门的 schema 或数据格式。指南二启动一个新单文件包的摩擦应最小化语法要容易记住、尽量简短这样人们才更愿意在没有工具辅助的一次性、实验性、原型场景中使用它。由此推导嵌入的 manifest 是可选的这也意味着不能要求用户必须指定edition单文件包不应被自动发现为 workspace 成员否则用户在任何地方创建一个文件来试想法的工作流会被破坏除非 workspace 恰好也拥有该文件。指南三诊断信息应指向单文件包本身Cargo/rustc 的诊断与消息包括cargo metadata的输出都应基于单文件包本身而不是任何临时文件这样消息更容易理解体验更一流由此很可能需要让 rustc 直接理解单文件包而不是由 cargo 把.rs内容拆到临时文件再交给 rustc——否则错误信息会指向错误的文件很可能还需要处理toml_edit返回的错误以便基于原始源码渲染 manifest 错误这就需要精确的 span 信息。这三条指南后来被 text/3502-cargo-script.md 的 Rationale 部分完整继承是贯穿整个设计的价值基线。六、Drawbacks代价与风险代码渗透面广该特性会渗透到 cargo 代码库的很多角落。虽然作者对它有稳定路径有信心且预期不会拖太久但在稳定之前这些成本换来的收益有限。稳定后维护负担一旦稳定单文件包会扩大 cargo 的维护与支持面。不预留语法不会为build.rs、[lib]支持、proc-macro 等后续功能预留语法。其假设是当用户需要这些能力时应该使用多文件包。后续 text/3502-cargo-script.md 在 Drawbacks 中又补充了隐式 manifest 内容不直观、target/无界增长、锁文件藏在 target 目录导致用户感知不到旧依赖、脚本移动即丢弃构建缓存等问题。七、Rationale 与备选方案范围Scope之争只做单文件包cargo-script工具家族有一个单一命令既能运行带嵌入 manifest 的.rs文件也能对命令行参数做求值--expr、--loop。这种去掉嵌入 manifest 之外的行为很像一个去掉了 REPL 的脚本环境——理论上可以按未来支持 REPL 的方向来设计。但 RFC 认为不应该.rs文件与 REPL / CLI 参数求值的需求不同例如它们获取依赖定义的方式就不一样REPL 是个大得多的课题需要引入大量与.rs文件无关的交互行为Rust 的 REPL 是相当模糊的未来可能性现在为它做设计为时过早。因此本 eRFC 的范围被限定为运行单文件 Rust 包。第一方 vs 第三方实现RFC 选择把它做成第一方cargo 内置功能理由包括标准化该约定从而获得更大的互操作性默认实现能保证人们真的去用它。文中举例clap收到过一个用cargo-play脚本写的复现用例但因为用户不愿再装一个陌生的工具而未被使用提升整体体验无需第三方命令去复刻对各种功能的支持包括cargo test等内置命令、cargo expand等第三方命令、rust-analyzer等编辑器/IDE 集成虽然其他第三方 cargo 命令未必立刻支持单文件包但第一方支持会鼓励它们跟进。同时这仍为第三方实现保留了差异化与实验空间更低的缓存开销替代缓存机制隐式main支持类似 doc-comment 示例为隐式main提供模板支持定制use、extern、#[feature]等简短依赖语法例如//# serde_json *优先其他工作流如运行时性能。八、Prior art同类方案全景RFC 引用 Single-file scripts that download their dependencies 作为整个领域的全景参考并详细列举了既有方案。Rust 生态内的现有方案cargo-script使用.crs扩展名在cargodoc-comment 围栏代码块中放部分 manifest或用注释指令放依赖通过run-cargo-script处理 shebang 和 Windows 文件关联性能上共享CARGO_TARGET_DIR复用依赖构建提供--expr expr把表达式包进 block以{:?}打印结果、--dep标志、--loop expr每行运行一个闭包、--test等补充标志、--force强制重建与--clear-cache通过若干环境变量与脚本通信。cargo-scripter / cargo-eval在 cargo-script 基础上多提交的派生版本。rust-script上述方案的进一步演化扩展名改为.ers/.rs主场景是一个无子命令的单一二进制便于直接运行支持隐式main含async main实现方式与 rustdoc 不同提供--toolchain-version标志。cargo-play允许多文件脚本第一个指定的文件是main依赖语法为//# serde_json *其余功能看似是 cargo-script 的子集。cargo-wopcargo wop之于单文件 Rust 脚本正如cargo之于多文件 Rust 项目依赖语法为 doc-comment 围栏代码块。Rust 相关方案Playground包含 top 100 crate。Rust Explorer用注释语法指定依赖。runner全局Cargo.toml通过runner --add dep添加依赖提供全局可编辑的 prelude / 模板-e expr、-i expr消费并打印迭代器值、-n expr每行运行。evcxr伞项目包含 REPL 与 Jupyter kernel需要显式选择panic 后不退出以:开头的表达式是 REPL 命令对引用的使用有限制。irustRust REPL:开头是 REPL 命令全局用户可编辑 prelude crate。papyrust并非单文件只是为 cargo 包提供快速缓存。其他语言的借鉴Ddubdub hello.d是dub run --single hello.d的简写manifest 放在文件顶部的普通嵌套块注释中以dub.sdl:为头。JavaJEP 330启动单文件源代码程序jbang 支持jbang init模板、jbang edit推荐编辑器配置、注释指令控制依赖与编译选项//DEPS ...、//COMPILE_OPTIONS ...、//NATIVE_OPTIONS ...、//RUNTIME_OPTIONS ...、运行 markdown 中的代码块、--code执行命令行代码、从 stdin 接受脚本。Haskellrunghc/runhaskell允许用文件主名去扩展名传入cabal 的单文件 Haskell 脚本命令就是cabal可能遇到与子命令同名的怪问题manifest 放在以cabal:开头的多行注释中stack script把stack作为#!快捷方式。跨语言scriptisto 支持任意编译语言用注释指令给出构建命令nix-script 是其 Nix 版可用任意 Nix 依赖。这份 prior art 清单说明单文件脚本 依赖管理是广泛存在的需求cargo 单文件包借鉴了多语言经验。九、未决问题Unresolved questionseRFC 特别希望通过实验性实现来解决以下问题#!行用什么命令用cargo的话它与内置命令、别名、外部命令的优先级如何用别的名字叫什么若用cargo-foocargo foo与cargo-foo行为不一致怎么办因为cargo foo这种带空格的形式跨平台很难写进#!。如何压低构建时间例如使用集中的CARGO_TARGET_DIR例如在脚本间锁定相近依赖以复用更多缓存。默认RUST_BACKTRACE设置对单文件包的使用场景有何影响是否值得专门处理。运行环境归属单文件包应在当前工作目录的环境.cargo/config.toml、rust-toolchain.toml中运行像cargo run还是在固定位置运行更像cargo install。如何嵌入 manifest对新用户是否显而易见、是否容易记忆和手写、cargo add等工具能否机器化编辑、必须仍是合法 Rust 代码、锁文件可能也需要复用这种附加元数据的方式。如何处理锁文件在单文件、易复制粘贴的诉求与 Rust 对可复现性的期待之间权衡。共享是否容易、Cargo.lock在复现用例中多重要、多余文件是否令人困扰、stdin 场景如何存锁文件而不冲突、只读的单文件包如无 root 权限运行/usr/bin/package.rs怎么办未来单文件包加入 workspace 后可改用 workspace 的Cargo.lock。如何处理package.edition平衡零样板、现代体验与可复现性没有 backing 文件从 stdin 读入时如何处理。以及一些更小的问题是否支持显式 stdin-或隐式 stdin是否在最初 MVP 就支持 workspace单文件包是否需要独立文件扩展名Windows 上应注册哪些文件关联单文件包不会被自动发现例如cargo test是cargo test --manifest-path Cargo.toml的简写能否让在单文件包上运行 cargo 命令更便捷RFC 有意不给出这些问题的潜在答案以便把讨论焦点集中在提议的实验上。十、未来可能性Future possibilities隐式main支持与 doc-comment 示例类似未来可以支持隐式main。理想情况下应在语言层面实现以保证 Playground、rustdoc、cargo 三处体验统一且 cargo 可以直接运行文件而无需写中间文件写中间文件在遇到顶层extern历史写法或 bin 级属性时会很脆弱。行为可由 edition 控制。REPL在 CLI 侧可以命名为cargo shell在你当前的包内进入交互式 shell加载既有依赖含 dev 依赖并可自然配备--eval expr标志。理想情况下这个 REPL 还支持类似python -i file的用法——不运行既有代码而是把特定文件的 API 项暴露出来便于对大型项目中的私有代码做交互式白盒测试。十一、仓库内的后续演进从 eRFC 到正式 RFC理解 3424 的价值还需要把它放在仓库的演进脉络中看。它并非终点而是后续两份 RFC 的起点text/3502-cargo-script.md3424 的正式化版本RFC 3502。它把嵌入 manifest的语法从 doc-comment 围栏代码块改为frontmatter——文件顶部以---开闭的 TOML 块。同时它给出了更具体的参考级设计推断/默认 manifest 字段package.name取 slugified 文件名、package.edition默认当前版本并发出警告、缺失package.version时默认0.0.0且publish默认false、被禁止的 manifest 字段[workspace]、[lib]、[[bin]]、package.workspace、package.build等、默认的 out-of-line 目标目录$CARGO_HOME/target/hash-of-path、cargo foo的优先级方案内置命令 xor manifest → 用户别名 → 第三方命令、非终端场景自动--quiet等并记录了各备选方案edition 处理、命名、文件扩展名、锁文件位置的 disposition。text/3503-frontmatter.md单独定义 Rust 源码的frontmatter 语法本身供 RFC 3502 使用。它规定剥离 shebang 后允许 0 行以上空行以 3 个-开头、可跟 info-string 与换行内容被 rustc 忽略直到出现相同数量-的闭合行未闭合视为错误。info-string如cargo由外部工具定义而非 rustc 定义。文中还对比了 doc-comment、attribute、regular comment、macro、presentation streams 等七种备选语法及其被否决的理由。对照可见3424 确立的是目标与设计原则一流体验、低摩擦、诊断指向原文件而 3502/3503 落实的是具体语法与参考实现细节。例如 3424 中cargo --verbose ./hello_world.rs的下载输出在 3502 中则多了package.edition默认2021的警告行——这正是 3424不能强制用户写 edition这一原则的落地结果。十二、总结与阅读建议RFC 3424 是一份以问题为导向的实验性设计文档它界定了 cargo 单文件包的目标形态#!直跑、内嵌 manifest、cargo file.rs快捷方式列举了覆盖协作、互操作、原型、一次性工具四类场景的动机给出了创建、加依赖、布局三部分上手指南提出了三条贯穿始终的设计指南并坦诚列出了代价、备选方案、未决问题与未来方向。若要继续追踪该特性的落地建议按顺序阅读仓库内文档text/3424-cargo-script.mdeRFC 本体理解设计动机与原则text/3502-cargo-script.md正式 RFC查看最终敲定的语法、字段默认值与命令优先级text/3503-frontmatter.mdfrontmatter 语法规范理解---块的词法细节与备选语法。三者合起来构成了目标 — 方案 — 语法的完整设计链条也是理解 Cargo 单文件包特性的最佳入口。赞分享文档【免费下载链接】rfcsRFCs for changes to Rust项目地址https://gitcode.com/gh_mirrors/rf/rfcs点击查看免费下载相关推荐.NET SDK 深度解析dotnet run 直接运行 C 源文件的技术实现.NET SDK 深度解析dotnet run 直接运行 C 源文件的技术实现 前言 在传统的 .NET 开发中我们通常需要先创建项目文件.csproj开发工具CLI构建工具代码生成Rust 自定义测试框架Custom Test FrameworkseRFC 深度解读让 cargo test 变成可插拔的后处理框架Rust 自定义测试框架Custom Test FrameworkseRFC 深度解读让 cargo test 变成可插拔的后处理框架 Rust 官方 R文档Rust嵌入式开发工具cargo-binutilsRust嵌入式开发工具cargo binutils 项目基础介绍 cargo binutils 是一个为Rust语言开发的嵌入式项目提供的工具集它通过扩展C上一篇如何用Scrapegraph-ai快速分析招聘信息AI智能人力资源助手终极指南下一篇拯救你的Mamba模型序列化避坑指南与最佳实践创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考