为什么除非存在依赖冲突,否则从另一个板条箱重新导出 Rust 宏

ane*_*son 5 trace rust rust-cargo rust-tokio

我正在为公司内部使用编写一个 Rust 板条箱,它包装了优秀的 Tokiotracing板条箱,并带有一些附加功能。我不仅重新导出了 中的宏tracing,而且还添加了一些我自己的在内部调用tracing宏的宏。我的目标是让我们所有的其他内部 crate 仅依赖于我的包装 crate,而不必tracing在每个 crate 中明确引入依赖项。

这非常有效,直到我今天遇到一个问题,我花了几个小时来隔离。我做了一个最小的例子来展示这里的行为。

在这个例子中,我有一个带有两个 crate 的工作区:my-logger-crate它包装tracing和公开一个 macro print_trace!,一个二进制 cratemy-binary-crate依赖于my-logger-crate,并在main函数内调用宏。

Cargo.toml用于my-logger-crate非常简单; 我添加到自动生成的骨架中的唯一内容是tracing依赖项:

[package]
name = "my-logger-crate"
version = "0.1.0"
authors = ["Adam Nelson <anelson@users.noreply.github.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
tracing = "0.1.12"
Run Code Online (Sandbox Code Playgroud)

这是my-logger-crate宏:

[package]
name = "my-logger-crate"
version = "0.1.0"
authors = ["Adam Nelson <anelson@users.noreply.github.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
tracing = "0.1.12"
Run Code Online (Sandbox Code Playgroud)

这由 使用my-binary-crate,它Cargo.toml也很简单:

[package]
name = "my-binary-crate"
version = "0.1.0"
authors = ["Adam Nelson <anelson@users.noreply.github.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
my-logger-crate = { path = "../my-logger-crate" }

# If the following line is uncommented, then the call to `print_trace!` in `main()` fails to compile.
# tower = "0.3"
Run Code Online (Sandbox Code Playgroud)

这是 中的main()函数my-binary-crate

/// Re-export everything in the `tracing` crate
pub use ::tracing::*;

/// A version of the `trace!` macro that sets a special target
#[macro_export]
macro_rules! print_trace {
    ($($arg:tt)*) => (
        // Invoke the `trace!` macro which is defined in the `tracing` crate but which has been
        // re-exported from this crate, so that downstream callers don't have to take an explicit
        // dependency on `tracing`
        $crate::trace!(target: "console", $($arg)*)
    )
}
Run Code Online (Sandbox Code Playgroud)

如果my-binary-crate没有任何冲突的依赖项,这将编译并运行良好。

但是看看如果我们towermy-binary-crate/Cargo.toml文件中有依赖会发生什么:

[package]
name = "my-binary-crate"
version = "0.1.0"
authors = ["Adam Nelson <anelson@users.noreply.github.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
my-logger-crate = { path = "../my-logger-crate" }

# If the following line is uncommented, then the call to `print_trace!` in `main()` fails to compile.
tower = "0.3"
Run Code Online (Sandbox Code Playgroud)

这是发生的事情:

[package]
name = "my-binary-crate"
version = "0.1.0"
authors = ["Adam Nelson <anelson@users.noreply.github.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
my-logger-crate = { path = "../my-logger-crate" }

# If the following line is uncommented, then the call to `print_trace!` in `main()` fails to compile.
# tower = "0.3"
Run Code Online (Sandbox Code Playgroud)

print_trace!宏扩展出来的tracing::trace!宏,然后扩展到tracing::event!,然后扩展到代码来记录事件,其中包括对呼叫tracing::...。在我添加该tower依赖项之前,所有这些都可以正常工作,因此显然编译器能够tracing::...正确解析,即使my-binary-cratetracing自身没有直接依赖关系。

我怀疑这与依赖冲突有关。 tower拉入一个很长的依赖树,包括tower-buffer,它本身依赖于tracing = "0.1.2"

我对 Cargo 如何解决这个看似冲突的理解是它会使用tracingversion 0.1.12,因为我的日志记录箱明确依赖于 version =0.1.12,并tower-buffer指定了0.1.2,它等效于^0.1.2. 但我不明白为什么添加这个额外的依赖树会破坏我的宏的使用。

目前,我已经通过tracing在所有使用我的日志记录箱的下游箱中添加显式依赖来解决它,但这远非理想。

我在启用宏跟踪的情况下运行:

fn main() {
    println!("Hello, world!");

    // Log a trace event with the logging system
    my_logger_crate::print_trace!("this is a trace message from print_trace!");
}
Run Code Online (Sandbox Code Playgroud)

不足为奇的是,该错误是试图调用tracing::__mk_format_string!,这失败,因为没有这样的箱子tracing被引用my-binary-crate。奇怪的是,为了解决这个错误,tracing必须评估其他几个宏。也许这是一个错误tracing,它应该是$crate::__mk_format_string!。但是,在我添加towermy-binary-crate.