Почему макрос Rust реэкспортируется из другого ящика, если нет конфликта зависимостей - PullRequest
3 голосов
/ 19 февраля 2020

Я пишу корзину Rust для внутреннего использования компанией, которая упаковывает превосходную коробку Tokio tracing с небольшим количеством дополнительных функций. Я не только повторно экспортирую макросы в tracing, но также добавляю несколько своих собственных, которые внутренне вызывают макросы tracing. Моя цель - позволить всем нашим внутренним ящикам зависеть только от моего ящика-обертки, без необходимости явно вставлять tracing зависимости в каждом ящике.

Это работает очень хорошо, пока я не столкнулся с проблемой сегодня мне потребовалось несколько часов, чтобы изолировать. Я сделал минимальный пример, чтобы показать поведение здесь .

В этом примере у меня есть рабочее пространство с двумя ящиками: my-logger-crate, который упаковывает tracing и предоставляет макрос print_trace!, и двоичный ящик my-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"

Это макрос my-logger-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)*)
    )
}

Используется 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"

А вот функция main() в my-binary-crate:

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!");
}

Если my-binary-crate не имеет конфликтующих зависимости, это компилируется и работает нормально.

Но посмотрите, что произойдет, если у нас есть зависимость от tower в файле 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"

Вот что происходит:

Compiling my-binary-crate v0.1.0 (/home/cornelius/scrap/e2780c4bd3dfa24758fcfd93e225b100/my-binary-crate)
error[E0433]: failed to resolve: use of undeclared type or module `tracing`
 --> my-binary-crate/src/main.rs:5:5
  |
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ use of undeclared type or module `tracing`
  |
  = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

Макрос print_trace! расширяется до макроса tracing::trace!, который затем расширяется до tracing::event!, а затем расширяется до кода для регистрации события, которое включает в себя звонки на tracing::.... Все это прекрасно работает, прежде чем я добавлю эту tower зависимость, поэтому ясно, что компилятор может правильно разрешить tracing::..., даже если my-binary-crate не имеет прямой зависимости от tracing.

Я подозреваю, что это связано с конфликтом зависимостей. tower извлекает длинное дерево зависимостей, включая tower-buffer, которое само зависит от tracing = "0.1.2".

Мое понимание того, как Car go разрешает этот кажущийся конфликт, заключается в том, что он будет использовать tracing версию 0.1.12, поскольку мой ящик для журналов явно зависит от версии =0.1.12, а tower-buffer указывает 0.1.2, что эквивалентно ^0.1.2. Но я не понимаю, почему добавление этого дополнительного дерева зависимостей нарушает использование моего макроса.

На данный момент я обошел его, добавив явные зависимости на tracing во все нижележащие ящики, которые используют мой ящик для журналов, но это далеко от идеала.

Я бежал с включенной трассировкой макросов:

$ RUSTFLAGS="-Z macro-backtrace" cargo +nightly test
   Compiling my-binary-crate v0.1.0 (/home/cornelius/scrap/e2780c4bd3dfa24758fcfd93e225b100/my-binary-crate)
error[E0433]: failed to resolve: use of undeclared type or module `tracing`
   --> <::tracing::macros::__mk_format_args macros>:78:17
    |
1   |    /  (@ { $ (,) * $ ($ out : expr), * $ (,) * }, $ fmt : expr, fields : $ (,) *) =>
2   |    |  { format_args ! ($ fmt, $ ($ out), *) } ;
3   |    |  (@ { $ (,) * $ ($ out : expr), * }, $ fmt : expr, fields : $ ($ k : ident) . +
4   |    |   = ? $ val : expr $ (,) *) =>
...      |
78  |    |          (@ { }, tracing :: __mk_format_string ! ($ ($ kv) *), fields : $
    |    |                  ^^^^^^^ use of undeclared type or module `tracing`
79  |    |           ($ kv) *)
80  |    |      }
81  |    |  } ;
... [a lot of macro expansions snipped]

Неудивительно, что ошибка - это попытка вызвать tracing::__mk_format_string!, которая терпит неудачу, потому что нет такого ящика tracing, на который ссылается my-binary-crate. Странно то, что для получения этой ошибки нужно было проанализировать несколько других макросов в tracing. Возможно, это ошибка в tracing, и она должна быть $crate::__mk_format_string!. Однако я до сих пор не понимаю, почему это работает, пока я не добавлю tower зависимость от my-binary-crate.

...