在实现过程宏时出现了循环依赖的包问题

9
我尝试实现一个 proc_macro Dump,类似于 serdes 的 Serialize
为此,我有一个包含我的“基本”结构体(在这种情况下是 P1P2)的 crate foo,这些结构体应该只能被转储。
接下来,我有一个包含过程宏本身的 crate foo_derive
因为我想支持多种格式,所以我有第三个 crate foo_dump,其中包含 Dump 的 trait 定义(例如,可以转储此结构体)和 Dumper(这是后端应该实现的内容)。
直到这一点都非常简单。
当我现在想要编译它时,我得到了这个错误:
$ cargo build
error: cyclic package dependency: package `foo v0.1.0 (/tmp/tmp.u34pI5J6qd/example/foo)` depends on itself. Cycle:
package `foo v0.1.0 (/tmp/tmp.u34pI5J6qd/example/foo)`
    ... which is depended on by `foo_dump v0.1.0 (/tmp/tmp.u34pI5J6qd/example/foo_dump)`
    ... which is depended on by `foo_derive v0.1.0 (/tmp/tmp.u34pI5J6qd/example/foo_derive)`

我不知道在这个crate中使用依赖的正确方式是什么。目前我的做法是:

dependencies

而这显然是不可能的。

我错过了什么?我必须怎样打破依赖循环?


(mcve@github)

翻译为:

({{链接1: mcve@github}})

/Cargo.toml

[workspace]
members = [ 
    "foo",
    "foo_derive",
    "foo_dump",
]

/foo/Cargo.toml

[package]
name = "foo"
version = "0.1.0"
edition = "2018"

[dependencies]
foo_derive = { path = "../foo_derive" }

/foo/src/lib.rs

use foo_derive::Dump;

struct P1;
struct P2;

#[derive(Dump)]
struct Bar {
    primitive_one: P1,
    primitive_two: P2,
}

/foo_dump/Cargo.toml

[package]
name = "foo_dump"
version = "0.1.0"
edition = "2018"

[dependencies]
foo = { path = "../foo" }

/foo_dump/src/lib.rs

use foo::{P1, P2};

pub trait Dumper {
    fn dump_p1(&mut self, value: &P1);
    fn dump_p2(&mut self, value: &P2);
}

pub trait Dump {
    fn dump<D: Dumper>(&self, d: D);
}

impl Dump for P1 {
    fn dump<D: Dumper>(&self, d: D) {
        d.dump_p1(self);
    }
}

impl Dump for P2 {
    fn dump<D: Dumper>(&self, d: D) {
        d.dump_p2(self);
    }
}

/foo_derive/Cargo.toml

[package]
name = "foo_derive"
version = "0.1.0"
edition = "2018"

[lib]
proc-macro = true

[dependencies]
syn = "*"
quote = "*"
foo_dump = { path = "../foo_dump" }

/foo_derive/src/lib.rs

extern crate proc_macro;

use quote::quote;
use proc_macro::TokenStream;
use syn::DeriveInput;

#[proc_macro_derive(Dump)]
pub fn derive_dump(input: TokenStream) -> TokenStream {
    let input = syn::parse_macro_input!(input as DeriveInput);
    let name = &input.ident;

    quote!(
        impl foo_dump::Dump for #name {
            fn dump<D: foo_dump::Dumper>(&self, d: D) {
                unimplemented!()
            }
        }
    ).into()
}

7
你的派生(derive)模块不应该依赖于任何东西,因为它只处理代码生成。 - Boiethios
1个回答

2
感谢@Boiethious在聊天中的帮助,我得以想出一个解决方案,其中涉及引入一个名为foo_core的新包,其中包含结构体P1P2
所以我做的是:
  • P1P2foo中移除,并将它们放入foo_core
  • foo_derive中删除对foo_dump的依赖,使其仅依赖于synquote
  • foofoo_dump中添加foo_core作为依赖项
  • foo_dump的依赖项添加到foo
您可以在Git历史记录中查看变更的完整列表。
最终依赖链现在如下所示:

depency graph


如果你有更好的解决方案,请随意创建一个!我对任何可用的解决方案都感兴趣。 - hellow
3
需要使用foo_core吗?你可以将东西放在foo_dump中。 - Boiethios
有趣...我认为可以把我需要的东西放在foo_dump里。我得看看我的真实世界例子,但是没错,那将是一个更简单的方法。 - hellow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接