我希望有几个特性与一些默认实现(硬要求),相互调用.当然,一个简单的方法是行不通的,因为特征A有特征B作为父,而特征A有特征B作为父. turtle 一路下来.我该怎么办?(我不能将这些特性组合在一起,它们应该在不同的文件中.

不工作的代码,证明了这个问题,将是

use std::fmt::Display;

trait A: B + Display {
    fn a(&self, count: i32) -> String {
        if count == 0 {
            String::new()
        } else {
            format!("[{}]a{}", self, self.b(count - 1))
        }
    }
}
impl A for String {}

trait B: A + Display {
    fn b(&self, count: i32) -> String {
        if count == 0 {
            String::new()
        } else {
            format!("[{}]b{}", self, self.a(count - 1))
        }
    }
}
impl B for String {}

fn main() {
    println!("{}", "Hello".to_string().a(3));
}

推荐答案

根据实际的最终目标,那么通过使用辅助特性和blanket implementation来做你正在做的事情会更容易.

简而言之,与其这样做:

trait Foo {
    fn foo(&self) {
        println!("default foo");
    }
}

impl Foo for String {}

然后你引入一个trait HasFoo并使用它触发一个blanket implementationFoo.看起来像这样:

trait Foo {
    fn foo(&self);
}

trait HasFoo {}

impl<T> Foo for T
where
    T: HasFoo,
{
    fn foo(&self) {
        println!("default foo");
    }
}

impl HasFoo for String {}

我们可以对您的示例执行相同的操作,通过引入trait HasAB来触发ABblanket implementation:

(Rust Playground)

trait HasAB {}

trait A {
    fn a(&self, count: i32) -> String;
}

trait B {
    fn b(&self, count: i32) -> String;
}

impl<T> A for T
where
    T: HasAB + Display,
{
    fn a(&self, count: i32) -> String {
        if count == 0 {
            String::new()
        } else {
            format!("[{}]a{}", self, self.b(count - 1))
        }
    }
}

impl<T> B for T
where
    T: HasAB + Display,
{
    fn b(&self, count: i32) -> String {
        if count == 0 {
            String::new()
        } else {
            format!("[{}]b{}", self, self.a(count - 1))
        }
    }
}

现在你只需要执行以下操作,然后你的示例编译:

impl HasAB for String {}

此外,由于我们取消了A: BB: A的界限.然后,你现在可以为不实现B的类型使用impl A,反之亦然.你以前做不到.

您仍然可以手动实现每个特征.不强制为所有类型实现所有它们.


否则,当你在处理违约问题时.然后,将代码移到不同的函数中是很常见的.就像这样:

impl A for Foo {
    fn a(&self, count: i32) -> String {
        default_a_where_b(self, count)
    }
}

fn default_a_where_b<T>(t: &T, count: i32) -> String
where
    T: B + Display,
{
    if count == 0 {
        String::new()
    } else {
        format!("[{}]a{}", t, t.b(count - 1))
    }
}

如果您想摆脱样板,那么您可以引入一个宏:

macro_rules! impl_A_where_B {
    ($t:ty) => {
        impl A for $t {
            fn a(&self, count: i32) -> String {
                default_a_where_b(self, count)
            }
        }
    };
}

and vice versa for 100.

现在你只需做:

impl_A_where_B!(Foo);

由于我们really没有specialization也没有negative trait bound,所以在我们想要默认实现和双向边界A: BB: A的情况下,它就更难兼顾这些情况.

如果是因为您希望同时实现这两个特征,那么您可以改为在函数上指定该界限.然后,您可以避免循环错误:

fn f<T>(t: &T) where T: A + B {}

总的来说,Rust并没有真正玩得很好(还?)你要做的事在现实中,你可能会更好地 Select 一个完全独立的设计.然而,如果没有更具体的例子,很难提出替代解决方案.

Rust相关问答推荐

为什么迭代器上的`. map(...)`的返回类型如此复杂?

在特征中使用Async时,如何解决不透明类型`impl Future<;out=self>;`不满足其关联的类型边界和警告?

亚性状上位性状上的 rust 病伴生型界限

无法将记录器向下转换回原始 struct

Gtk4-rs:将监视器作为gdk::monitor获取,而不是作为glib::对象获取

一种随机局部搜索算法的基准(分数)

对于已经被认为是未定义行为的相同数据,纯粹存在`&;[u32]`和`&;mut[u32]`吗?

为什么';t std::cell::ref使用引用而不是非空?

变量需要parse()中的显式类型

循环访问枚举中的不同集合

什么时候使用FuturesOrdered?

返回Result<;(),框<;dyn错误>>;工作

如何在 `connect_activate()` 之外创建一个 `glib::MainContext::channel()` 并将其传入?

为什么某些类型参数仅在特征边界中使用的代码在没有 PhantomData 的情况下进行编译?

std::vector::shrink_to_fit 如何在 Rust 中工作?

如果不满足条件,如何在 Rust 中引发错误

为什么 no_std crate 可以依赖于使用 std 的 crate?

通用函数中的生命周期扣除和borrow (通用测试需要)

有没有更好的方法来为拥有 DIsplay 事物集合的 struct 实现 Display?

如何制作具有关联类型的特征的类型擦除版本?