error[E0252]: the name tls is defined multiple times
主线位置:请求主线里 TLS 握手那一步用哪套代码,是编译期由 feature 选的。
pingora-core怎么用 cfg 把某个后端改名成tls、any_tls和openssl_derived两个内部 feature 干什么,见怎么读这一卷和 TLS。本页讲两个后端同时打开时的报错,以及它常见的来路。
flowchart TB app["应用 openssl"] --> uni["feature 统一"] dep["依赖 rustls"] --> uni uni --> core["pingora-core"] core --> a["pub use openssl as tls"] core --> b["pub use rustls as tls"] a --> e1["E0252"] b --> e1 core --> glob["tls 模块 glob 导出"] glob --> e2["E0659 等 74 个"]
实验 crate exp-pingora/pita-twotls(报错)和 exp-pingora/pita-onetls(修好的);M2 Max、macOS 27、rustc 1.99.0,debug。
现象
最直接的写法是在 pingora 的 feature 里同时写 openssl 和 rustls。更常见、也更难发现的是两处各开一个:应用通过门面 crate 要 openssl,另一行依赖(或者某个间接依赖)直接对 pingora-core 要 rustls:
[dependencies]
pingora = { version = "=0.9.0", features = ["openssl", "proxy"] }
pingora-core = { version = "=0.9.0", features = ["rustls"] }
src/main.rs 只有一个空的 fn main() {},cargo build:
error[E0252]: the name `tls` is defined multiple times
--> ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/pingora-core-0.9.0/src/lib.rs:117:9
|
114 | pub use pingora_openssl as tls;
| ---------------------- previous import of the module `tls` here
...
117 | pub use pingora_rustls as tls;
| ^^^^^^^^^^^^^^^^^^^^^ `tls` reimported here
|
= note: `tls` must be defined only once in the type namespace of this module
help: you can use `as` to change the binding name of the import
|
117 | pub use pingora_rustls as other_tls;
...
error[E0659]: `Acceptor` is ambiguous
--> ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/pingora-core-0.9.0/src/listeners/mod.rs:87:11
|
87 | use tls::{Acceptor, TlsSettings};
...
error: could not compile `pingora-core` (lib) due to 75 previous errors
- 75 个错误全在
pingora-core里,没有一个指向用户的代码。按错误码数:E0659(is ambiguous)55 个、E0308 12 个、E0560 4 个,E0252、E0061、E0277、E0609 各 1 个。 - rustc 的 help 建议把第二个改名成
other_tls。那一行在 cargo 缓存里的依赖源码中,改不了,也不该改。 - 用户代码一行都没写,这个错误和「代码怎么写」无关,只和依赖图里打开了哪些 feature 有关。
出处
pingora-core/src/lib.rs 的这几行是并列的,没有互斥条件:
// If both openssl and boringssl are enabled, prefer boringssl.
// This is to make sure that boringssl can override the default openssl feature
// when this crate is used indirectly by other crates.
#[cfg(feature = "boringssl")]
pub use pingora_boringssl as tls;
#[cfg(feature = "openssl")]
pub use pingora_openssl as tls;
#[cfg(feature = "rustls")]
pub use pingora_rustls as tls;
#[cfg(feature = "s2n")]
pub use pingora_s2n as tls;
#[cfg(not(feature = "any_tls"))]
pub use protocols::tls::noop_tls as tls;
任意两个后端同时打开,就有两行 pub use ... as tls 生效,这是 E0252。protocols/tls/mod.rs、listeners/tls/mod.rs 再按 feature 把各后端的子模块 pub use ...::* 进来,同名的 Acceptor、TlsSettings 互相遮挡,这是那 55 个 E0659。
Cargo 的 feature 是按包统一的:整个依赖图里只编译一份 pingora-core,它打开的 feature 是所有依赖方要求的并集。所以「应用要 openssl、某个库要 rustls」和「应用自己写了两个」结果一样。注释里说的「prefer boringssl」在 0.9.0 的代码里不存在,同时开 openssl 和 boringssl 也会撞上第 111 行和第 114 行。
0.1.0 里 openssl 那一行写的是 #[cfg(all(not(feature = "boringssl"), feature = "openssl"))]:boringssl 打开时 openssl 那行不生效,注释说的「boringssl can override the default openssl feature」就是这样实现的。b939776(2024-10-11,「Refactor openssl code one more time before rustls integration」,没有 PR 编号,首次进入 0.4.0)为接入 rustls 重排这几行时,把它改成了 #[cfg(feature = "openssl")],加上 rustls 那一行,dummy_tls / some_tls 也改名成今天的 noop_tls / any_tls。互斥条件从那时起就没了,注释原样留到了 0.9.0。
修法
整个依赖图里只留一个 TLS 后端。pita-onetls 删掉直接对 pingora-core 要 rustls 的那一行,pingora-core 只通过 pingora 进来:
[dependencies]
pingora = { version = "=0.9.0", features = ["openssl", "proxy"] }
pingora-core = "=0.9.0"
Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.58s
如果第二个后端来自一个间接依赖,就要找到它并关掉那个 feature(多数库会把 TLS 后端做成可选的 feature,default-features = false 再挑一个);找不到办法关的话,只能让应用跟着它用同一个后端。
怎么确认没中招
看 pingora-core 最终打开了哪些 feature:
$ cargo tree -i pingora-core -f '{p} [{f}]' --depth 0
pingora-core v0.9.0 [any_tls,default,openssl,openssl_derived,ouroboros,pingora-openssl,pingora-rustls,rustls]
这是 pita-twotls 的输出,openssl 和 rustls 同时在里面。pita-onetls 是 [any_tls,default,openssl,openssl_derived,pingora-openssl]。方括号里 openssl、boringssl、rustls、s2n 应当正好出现一个;一个都没有是另一个坑,见 No tls feature was specified。
要知道是谁打开的,用 cargo tree -e features -i pingora-core,找 pingora-core feature "rustls" 下面那一行:
│ └── pingora-core feature "rustls"
│ └── pita-twotls v0.1.0 (~/exp-pingora/pita-twotls) (*)
小结
pingora-core用并列的pub use <后端> as tls选 TLS,没有互斥;开两个后端是 E0252 加一串 E0659,共 75 个错误,全在依赖里。- feature 在整个依赖图里统一,应用和某个依赖各开一个也算两个。
- 「prefer boringssl」那段注释从 0.4.0(
b939776)起就不成立。 - 用
cargo tree -i pingora-core -f '{p} [{f}]' --depth 0确认只剩一个后端,用-e features找是谁开的。