rust 如何消除指向同一类型的不同/共有类型关联的歧义?

mzsu5hc0  于 2023-02-19  发布在  其他
关注(0)|答案(2)|浏览(81)

我有一系列相互关联的trait,并且包含更多的类型关联,这些类型关联应该是枚举,我在每个需要它的trait中重复Id类型关联,以避免像<<World as World>::Exit as Exit>::SpotId这样的深度嵌套的完全限定名,但这会导致混乱,因为我现在实现的行为依赖于这些"顶级" trait:

trait Accessible {
    type Context: Ctx;
    fn can_access(&self, ctx: &Self::Context) -> bool;
}
trait Id: Copy + Clone + Debug + Eq + Hash + Ord + PartialOrd {}
trait Location: Accessible {
    type LocId: Id;
    type ExitId: Id;
}
trait Exit: Accessible {
    type ExitId: Id;
    type SpotId: Id;
    type LocId: Id;
}
trait World {
    type Context: Ctx;
    type Location: Location;
    type Exit: Exit;
    type SpotId: Id;

    fn get_neighbors(&self, spot_id: Self::SpotId) -> &[Self::SpotId];
}
trait Ctx: Clone + Eq {
    type World: World<Context = Self, SpotId = Self::SpotId>;
    type SpotId: Id;
}

fn expand<T>(world: &impl World<Context = T>, ctx: &T, start: <T as Ctx>::SpotId)
    where T: Ctx,
{
    for spot in world.get_neighbors(start) { }
}

on the rust playground
这会在world.get_neighbors上产生错误:

| expected `World::SpotId`, found `Ctx::SpotId`
 = note: expected associated type `<impl World<Context = T> as World>::SpotId`
            found associated type `<T as context::Ctx>::SpotId`

并且在SpotId的一些其它用途上反之亦然。
有没有一种干净的方法来维护相互关联,并为(例如)SpotId指定一个简单的类型名,这样命名SpotId的哪个trait版本就不重要了?
我尝试添加更多的注解,以明确它们必须被推断为相同的类型:

trait World {
    type Context: Ctx<SpotId = Self::SpotId, World = Self>;
    type Location: Location + Accessible<Context = Self::Context>;
    type Exit: Exit<SpotId = Self::SpotId> + Accessible<Context = Self::Context>;
    type SpotId: Id;
}

但得到相同的误差。
我看过https://users.rust-lang.org/t/type-alias-for-readability-with-associated-traits/64044/2,它建议使用类型别名来管理完全限定名,但我不确定如何开始。

    • 更新**:可以通过类型参数指定一个类型满足两个要求:
fn expand<T, S>(world: &impl World<Context = T, SpotId = S>, ctx: &T, start: S)
    where T: Ctx<SpotId = S>, S: Id,
{
    for spot in world.get_neighbors(start) { }
}

但随着开发的继续,我的函数看起来更像是

fn explore<T, S, L, E>(
    world: &impl World<
        Context = T,
        SpotId = S,
        Exit = impl Exit<ExitId = E, SpotId = S> + Accessible<Context = T>,
        Location = impl Location<LocId = L> + Accessible<Context = T>,
    >,
    ctx: &T,
)
where
    T: Ctx<SpotId = S, LocationId = L, ExitId = E> + Debug,
    S: Id,
    L: Id,
    E: Id,
{ ... }

并且每当我添加另一个id类型的使用时,每个调用者都必须被修改(为了简洁,我在这里省略了一些)。

bogh5gae

bogh5gae1#

您所要做的就是要求这些关联的类型在fn expand的定义中是相同的类型,这可以通过引入另一个泛型参数来实现:

fn expand<T: Ctx<SpotId = S>, S>(
    world: &impl World<Context = T, SpotId = S>,
    ctx: &T,
    start: S
) {
    for spot in world.get_neighbors(start) { }
}

添加参数S并将CtxWorld约束为具有SpotId = S告诉编译器它们必须是相同的类型,以便允许调用此函数。
Playground

7dl7o3gd

7dl7o3gd2#

下面是最后的工作:忽略Accessible中的循环依赖,我重新组织类型,使World只有一个路径到每个Id类型,然后在trait函数中使用完全限定语法。然后,我更改函数中的泛型类型,使用实现place trait的类型,而不是Id,因为这样更容易编写trait边界。包括一些奇怪的情况,比如我添加的这个结构体,它只需要CtxSpotId,但必须提供它们相关的边界。

trait World {
    type Location: Location;
    type Exit: Exit<
        ExitId = <Self::Location as Location>::ExitId,
        LocId = <Self::Location as Location>::LocId,
        Context = <Self::Location as Accessible>::Context,
    >;

    fn get_neighbors(&self, spot_id: <Self::Exit as Exit>::SpotId) -> &[<Self::Exit as Exit>::SpotId];
}
trait Ctx {
    type World: World;
}

struct SpotAccess<T, S>
where
    T: Ctx,
    S: Id,
    <T::World as World>::Exit: Exit<SpotId = S>,
{ ... }

fn expand<W, T, E>(world: &W, ctx: &T, start: SpotAccess<T, E::SpotId>)
where
    W: World<Exit = E>,
    T: Ctx<World = W>,
    E: Exit<Context = T>,
{ ... }

我不确定World::Exit的绑定是否与World::Location的绑定相同,但它是必需的,但我现在将其保留。

相关问题