Rust in Everything
基于 pulldown-cmark 0.13.5

E0433: cannot find html in pulldown_cmark

主线位置:输出主线的入口 push_html,它所在的 html 模块整个由 feature 控制。四个 feature 各开什么见怎么读这一卷的「feature 各打开什么」,那里已经贴过这条报错;本页讲它是怎么来的、为什么在工作区里会晚一步才出现。

flowchart TB
  r["README 去掉命令行"] --> nd["default-features false"]
  nd --> g["getopts 关了"]
  nd --> h["html 也关了"]
  h --> e["E0433 或 E0432"]
  h --> ws{"工作区里"}
  ws -->|别的成员开默认| ok["整体能编"]
  ok --> p["单独 -p 时报错"]
  e --> fix["features html"]

实验 crate:exp-pulldown-cmark/pitb-nodefault(default-features = false)、pitb-htmlonly(修法)、pitb-ws(两个成员的工作区)。M2 Max、macOS 27、rustc 1.99.0。

现象

README 的「Build options」一节说:

By default, the binary is built as well. If you don't want/need it, then build like this: ... cargo add pulldown-cmark --no-default-features

库用户不需要命令行工具,照做,得到 pulldown-cmark = { version = "=0.13.5", default-features = false }。然后照 README 开头的「Example」写 pulldown_cmark::html::push_html(&mut html_output, parser):

error[E0433]: cannot find `html` in `pulldown_cmark`
  --> src/bin/path.rs:3:19
   |
 3 |   pulldown_cmark::html::push_html(&mut out, pulldown_cmark::Parser::new("*hello*"));
   |                   ^^^^ could not find `html` in `pulldown_cmark`
   |
note: found an item that was configured out
  --> ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/pulldown-cmark-0.13.5/src/lib.rs:84:9
   |
83 | #[cfg(feature = "html")]
   |       ---------------- the item is gated behind the `html` feature
84 | pub mod html;
   |         ^^^^

For more information about this error, try `rustc --explain E0433`.

写成 use pulldown_cmark::{html, Parser}; 时错误号换成 E0432,note 一样:

error[E0432]: unresolved import `pulldown_cmark::html`
 --> src/main.rs:1:22
  |
1 | use pulldown_cmark::{html, Parser};
  |                      ^^^^ no `html` in the root
  |
note: found an item that was configured out
...

rustc 的 note 已经指出了原因:html 模块被 cfg 掉了。

在工作区里,这条报错会晚到。 pitb-ws 有两个成员:a 照 README 写 default-features = false 并调用 push_html,b 普通地依赖 pulldown-cmark = "=0.13.5"。在工作区根目录 cargo build:

   Compiling a v0.1.0 (~/exp-pulldown-cmark/pitb-ws/a)
   Compiling b v0.1.0 (~/exp-pulldown-cmark/pitb-ws/b)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.29s

能编。单独编 a(cargo build -p a,或者把 a 发布出去、被别人依赖):

error[E0432]: unresolved import `pulldown_cmark::html`
error: could not compile `a` (bin "a") due to 1 previous error

原因

lib.rs 里 html 模块挂在 feature 上:

#[cfg(feature = "html")]
pub mod html;

Cargo.toml 的默认 feature 有两个:

default = ["getopts", "html"]
html = ["pulldown-cmark-escape"]

default-features = false 是一刀切:命令行工具要的 getopts 和库的 html 一起关掉。README 那句话只讲了前者。html 关掉以后 pulldown-cmark-escape 也不再是依赖,push_html、write_html_io、write_html_fmt 都不存在;解析器本身(Parser、Event、TextMergeStream)不受影响。

工作区里晚到,是因为 Cargo 的 feature 合并(feature unification):同一次构建里,同一个依赖只编一份,开的 feature 是所有请求的并集。b 要了默认 feature,a 拿到的 pulldown-cmark 里就有 html。只编 a 时没有 b 来补,问题才露出来。

📝NOTE
冷知识:这句 README 写的时候,关掉的只有命令行工具

「If you don't want/need it, then build like this: cargo build --no-default-features」来自 2017 年的 c224397「Remove unwanted conditional compilation」。那时默认 feature 只服务于命令行工具,这句话是对的。PR #519(8aab2f4「Make the html and escape modules optional」,首次发布在 0.10.0)把 default = ["getopts"] 改成 default = ["getopts", "html"],给 lib.rs 的 pub mod html 加上 #[cfg(feature = "html")],还把文档注释里的例子包进 # #[cfg(feature = "html")] { ... };README 的这一节没有动。后来 PR #904(1978ca3「chore(doc): use cargo add to instruct how to install this crate as dependency」,0.11.1)把旁边的 Cargo.toml 片段换成了 cargo add pulldown-cmark --no-default-features,意思没变。从 0.10.0 起,照这一节做的库用户都会丢掉 html。

修法

只想去掉 getopts,就把 html 写回来:

pulldown-cmark = { version = "=0.13.5", default-features = false, features = ["html"] }

pitb-htmlonly 用同样的 main.rs:

<p><em>hello</em></p>

cargo tree -e normal -f '{p} [{f}]',getopts 和它带来的 unicode-width 都不在了,html 和 escape crate 还在:

pitb-htmlonly v0.1.0 (~/exp-pulldown-cmark/pitb-htmlonly) []
└── pulldown-cmark v0.13.5 [html,pulldown-cmark-escape]
    ├── bitflags v2.13.2 []
    ├── memchr v2.8.3 [alloc,default,std]
    ├── pulldown-cmark-escape v0.11.0 []
    └── unicase v2.10.0 []

开着默认 feature 时,getopts 作为依赖只是多编译一个小库,库代码不用它,不影响运行;嫌它多,就用上面这一行。要 SIMD 的话一起写进 features,见上一页开了 simd,ARM 上什么都没变。

下一个大版本要再看一次。 main 上的 Cargo.toml 已经是 default = ["std", "getopts", "html"],新增的 std feature 也在默认里,并多了一个 hashbrown。按 main 的 Cargo.toml 和 README,default-features = false 到那时还会关掉 std;README 的「Build options」那句话在 main 上仍然没改。本卷没有编译 main 验证升级后要怎么写。

怎么检查

  • 看每个依赖实际开了什么: cargo tree -e features -i pulldown-cmark,或者 cargo tree -e normal -f '{p} [{f}]'。要调 push_html 的 crate,pulldown-cmark 后面要有 html。
  • 在工作区里,每个成员单独编一次: cargo build -p <成员>。别的成员的默认 feature 会掩盖这个问题,单独编、单独发布时才暴露。
  • 写 default-features = false 时把需要的 feature 列全。 对 0.13.5 来说,关掉默认 feature 会失去的公开 API 只有 html 模块;解析和事件流不受影响。

小结

  • 0.13.5 的默认 feature 是 getopts 和 html。README 说用 --no-default-features 去掉命令行工具,html 跟着关了,push_html 报 E0433(路径写法)或 E0432(use 写法),note 指向 #[cfg(feature = "html")]。
  • README 这句写于 2017 年,那时默认 feature 只有命令行用的;0.10.0(PR #519)把 html 加进默认后它就不对了。
  • 修法:default-features = false, features = ["html"]。
  • 工作区里别的成员开着默认 feature 时,feature 合并会把问题藏起来,单独 -p 构建才报错。

💬 关联讨论

发起讨论
加载中...