奇怪的重复通用特征模式:溢出评估需求 [英] Curiously recurring generic trait pattern: overflow evaluating the requirement

查看:15
本文介绍了奇怪的重复通用特征模式:溢出评估需求的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图用一堆字段实现一个通用结构,其中每个字段类型都应该知道整个结构的确切类型.这是一种策略模式.

I am trying to implement a generic structure with a bunch of fields, where each of the field types should know about the exact type of the whole structure. It's a sort of strategy pattern.

pub struct Example<S: Strategy<Example<S, D>>, D> {
    pub s: S,
    pub a: S::Associated,
    pub data: D,
}
pub trait Strategy<T> {
    type Associated;
    fn run(&self, &T);
}
pub trait HasData {
    type Data;
    fn data(&self) -> &Self::Data;
}

impl<S: Strategy<Self>, D> Example<S, D> {
//               ^^^^
// the complex code in this impl is the actual meat of the library:
    pub fn do_it(&self) {
        self.s.run(self); // using the Strategy trait
    }
}
impl<S: Strategy<Self>, D> HasData for Example<S, D> {
    type Data = D;
    fn data(&self) -> &D {
        &self.data
    }
}

我打算然后从上面的库"中实例化泛型:

I was planning to then instantiate the generics from the above "library":

pub struct ExampleStrat;
pub struct ExampleData;

impl<E: HasData<Data = ExampleData>> Strategy<E> for ExampleStrat {
    type Associated = ();
    fn run(&self, e: &E) {
        let _ = e.data();
        // uses ExampleData here
    }
}
let example = Example {
    s: ExampleStrat,
    a: (),
    data: ExampleData,
};
example.do_it();

在我的实际代码中,我有很多不同的策略"和多个数据字段,所以 Example 类型有一个令人印象深刻的泛型列表,如果库用户不需要明确(或至少不经常),而是可以只使用 HasData trait(及其关联类型,而不是泛型类型参数).

In my actual code I've got quite a few different "strategies" and also multiple data fields, so the Example type has an impressive list of generics, and I'm happy if the library user doesn't need to be explicit about them (or not often at least) and instead can just use the HasData trait (with its associated types, not generic type parameters).

如果 struct Example<S, D> 中没有类型绑定,这实际上会(出人意料地)正常工作,比我最初预期的要好得多(在 struct边界中与Self战斗).但是建议复制struct上的impl特征边界 当结构只应该与受约束的类型一起使用时,在我的情况下,我实际上需要它们能够为 使用 Associated 类型一个字段.

If there was no type bound in struct Example<S, D>, this would actually work (surprisingly) fine, much better than I has initially expected (after fighting with Self in the struct bounds). However it is recommended to duplicate the impl trait bounds on the struct when the struct is only supposed to be used with the constrained types, and in my case I actually need them to be able to use the Associated type for the a field.

现在编译器在抱怨

error[E0275]: overflow evaluating the requirement `main::ExampleStrat: Strategy<Example<main::ExampleStrat, main::ExampleData>>`
  --> src/main.rs:42:9
   |
42 |         a: (),
   |         ^^^^^
   |
   = note: required because of the requirements on the impl of `HasData` for `Example<main::ExampleStrat, main::ExampleData>`
   = note: required because of the requirements on the impl of `Strategy<Example<main::ExampleStrat, main::ExampleData>>` for `main::ExampleStrat`

我该如何解决这个问题?我是否正在尝试做一些不可能的事情,我做错了什么,或者它应该是可能的,但我成为了编译器错误?我的完整设计有缺陷吗?

How can I solve this? Am I trying to do something that is not possible, am I doing it wrong, or is it supposed to be possible but I am falling prey to a compiler bug? Is my complete design flawed?

推荐答案

首先,如果您避免在结构和特征的定义上设置特征边界,一切都会变得更加清晰.当事情变得复杂时,至少可以从同一个方向解决约束.

First of all, everything becomes a lot clearer if you avoid putting trait bounds on definitions of structs and traits. When things get complicated, the constraints are at least solved from the same direction.

pub struct Example<S, D, A> {
    pub s: S,
    pub a: A,
    pub data: D,
}

pub trait Strategy<T> {
    type Associated;
    fn run(&self, &T);
}

pub trait HasData {
    type Data;
    fn data(&self) -> &Self::Data;
}

impl<S, D, A> Example<S, D, A>
where
    S: Strategy<Self, Associated = A>,
{
    pub fn do_it(&self) {
        self.s.run(self);
    }
}

impl<S, D, A> HasData for Example<S, D, A>
where
    S: Strategy<Self, Associated = A>,
{
    type Data = D;
    fn data(&self) -> &D {
        &self.data
    }
}

您为 ExampleStrat 实施的 Strategy 如下所示:

Your implementation of Strategy for ExampleStrat looks like this:

impl<E: HasData<Data = ExampleData>> Strategy<E> for ExampleStrat {
    type Associated = ();
     // ...
}

这意味着您正在为所有可能的限定类型 E 定义它.类型检查器现在只能查看特征边界,这些边界又是通用的,并且只能用其他特征来表达,这些特征相互用作边界,因此类型检查器进入一个循环.通过给它一个你知道的具体类型来将它放入循环中.

What this means is that you are defining it for all possible qualifying types E. The type-checker can now only look at the trait bounds, which are again generic and only expressed in terms of other traits, which use each other as bounds, so the type-checker gets into a cycle. Put a block in the cycle by giving it a concrete type, which you know.

pub struct ExampleStrat;
pub struct ExampleData;

impl Strategy<Example<ExampleStrat, ExampleData, ()>> for ExampleStrat {
    type Associated = ();
    fn run(&self, e: &Example<ExampleStrat, ExampleData, ()>) {
        let _ = e.data();
        // uses ExampleData here
    }
}

fn main() {
    let example = Example {
        s: ExampleStrat,
        a: (),
        data: ExampleData,
    };
    example.do_it();
}

这篇关于奇怪的重复通用特征模式:溢出评估需求的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆