选择冲突特征实现的首选实现(使用负边界)

Dem*_*gos 9 traits rust

我正在开发一个 Rust 程序,我遇到了一个可以简化为以下情况的问题:

struct Pair<L, R> {
  left: L,
  right: R,
}

// Returns the first `u32` in the pair (only defined for pairs containing an u32)
trait GetU32 {
  fn get(&self) -> u32;
}

// This should also be used for `Pair<u32, u32>`
impl<R> GetU32 for Pair<u32, R> {
  fn get(&self) -> u32 {
    self.left
  }
}

impl<L> GetU32 for Pair<L, u32> {
  fn get(&self) -> u32 {
    self.right
  }
}

// impl GetU32 for Pair<u32, u32> {
//   fn get(&self) -> u32 {
//     self.left
//   }
// }

fn main() {
  let a: Pair<u8, u32> = Pair {left: 0u8, right: 999u32};
  assert_eq!(999u32, a.get());

  let b: Pair<u32, u8> = Pair {left: 999u32, right: 0u8};
  assert_eq!(999u32, b.get());

  let c: Pair<u32, u32> = Pair {left: 999u32, right: 0u32};
  assert_eq!(999u32, c.get());
}
Run Code Online (Sandbox Code Playgroud)

游乐场链接

我有一个包含两个字段的结构。如果一个(或两个)字段是u32,我想返回第一个u32。应在编译期间静态选择要使用的字段。

上面代码的问题是我无法表达哪个实现具有更高的优先级,并且它会导致案例冲突Pair<u32, u32>

error[E0119]: conflicting implementations of trait `GetU32` for type `Pair<u32, u32>`:
  --> crates/etwin_simple_user_pg/src/main.rs:20:1
   |
12 | impl<R> GetU32 for Pair<u32, R> {
   | ------------------------------- first implementation here
...
18 | default impl<L> GetU32 for Pair<L, u32> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Pair<u32, u32>`
Run Code Online (Sandbox Code Playgroud)

如何通过选择首选实现来解决此冲突?我可以使用夜间功能,例如专业化。

我研究了明确定义冲突的情况(注释代码),但这只导致了更多冲突。我追求的另一个解决方案是尝试使用专业化,但我无法将其应用到我的用例中。另一种解决方案是将第二个实现指定为负边界为impl<L: !u32> GetU32 for Pair<L, u32>(仅针对唯一为的特征定义u32.right,但负边界不存在。

我知道还有其他关于冲突特征实现的问题,但我没有发现冲突来自于在如此简单的情况下无法选择首选实现的问题。


编辑我想扩展我的问题,以提供有关我的实际问题和我当前正在使用的解决方案的更多背景信息。

我正在创建一个类似于 a 的结构,frunk::HList通过添加(或覆盖)服务来一点一点地构建 API 对象。该结构会记住注册了哪些服务并允许稍后检索它们。这一切都是静态发生的,因此编译器可以强制注册服务并知道哪个字段对应于它。(类似于上面的最小示例,编译器应该知道该对u32在哪个字段中有一个 and )。

由于我无法表达负界,因此我目前正在为我关心的负集中的每种类型实现辅助 getter(请参阅 LotB 的答案)。当我需要新类型时,这需要手动更新此结构的实现。在上面的示例中,如果我的类型是无符号整数,它将对应于以下代码:

error[E0119]: conflicting implementations of trait `GetU32` for type `Pair<u32, u32>`:
  --> crates/etwin_simple_user_pg/src/main.rs:20:1
   |
12 | impl<R> GetU32 for Pair<u32, R> {
   | ------------------------------- first implementation here
...
18 | default impl<L> GetU32 for Pair<L, u32> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Pair<u32, u32>`
Run Code Online (Sandbox Code Playgroud)

Dem*_*gos 9

正如问题中提到的,这种情况可以使用负界来解决。即使在 nightly 分支上,此功能尚不可用。

幸运的是,有一种解决方法可以通过组合两个现有的夜间功能来实现足够形式的负边界:auto_traitsnegative_impls

这是代码:

#![feature(auto_traits, negative_impls)]

auto trait NotU32 {}

// Double negation: `u32` is not a "not `u32`"
impl !NotU32 for u32 {}

struct Pair<L, R> {
  left: L,
  right: R,
}

// Returns the first `u32` in the pair (only defined for pairs containing an u32)
trait GetU32 {
  fn get(&self) -> u32;
}

// This should also be used for `Pair<u32, u32>`
impl<R> GetU32 for Pair<u32, R> {
  fn get(&self) -> u32 {
    self.left
  }
}

impl<L: NotU32> GetU32 for Pair<L, u32> {
  fn get(&self) -> u32 {
    self.right
  }
}

fn main() {
  let a: Pair<u8, u32> = Pair {left: 0u8, right: 999u32};
  assert_eq!(999u32, dbg!(a.get()));

  let b: Pair<u32, u8> = Pair {left: 999u32, right: 0u8};
  assert_eq!(999u32, dbg!(b.get()));

  let c: Pair<u32, u32> = Pair {left: 999u32, right: 0u32};
  assert_eq!(999u32, dbg!(c.get()));
}
Run Code Online (Sandbox Code Playgroud)

游乐场链接

由于我们无法使用impl<L: !u32> GetU32 for Pair<L, u32> { ... }(负界) 定义辅助 getter,因此我们使用标记特征将其定义NotU32impl<L: NotU32> GetU32 for Pair<L, u32> { ... }。这解决了问题:我们现在必须为除 之外的所有类型设置此标记特征u32。这就是auto_trait(为所有类型添加特征)和negative_impl(从某些类型中删除它)的出现。

这个答案的局限性在于,您现在无法Not<T>使用此方法定义通用特征。