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

Pick preferred implementation on conflicting trait implementation (using negative bounds)

我正在研究一个 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());
}

Playground link

我有一个包含两个字段的结构。如果一个(或两个)字段是 u32,我想 return 第一个 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>`

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

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

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


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

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

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

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

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

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

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

避免该问题的一个简单方法是只实现您需要的类型,而不是泛型。 所以不是通用的

impl<R> GetU32 for Pair<u32, R>

你在特定类型上实现,所以你控制每个特定组合。

impl GetU32 for Pair<u32, u8>

一个宏可以帮助定义各种impl而不是直接定义它们,如果你有很多类型和组合要处理,可以使具体定义的过程更容易。

macro_rules! impl_pair {
    ( $left:ident, $right:ident, $side:ident) => {
        impl GetU32 for Pair<$left, $right> {
            fn get(&self) -> u32 {
                self.$side
            }
        }
    }
}

impl_pair!(u32, u8, left);
impl_pair!(u8, u32, right);
impl_pair!(u32, u32, left);

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

幸运的是,有一种解决方法可以通过结合两个现有的夜间功能来实现足够形式的负边界:auto_traits and negative_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()));
}

Playground link

由于我们无法用 impl<L: !u32> GetU32 for Pair<L, u32> { ... }(负边界)定义次要 getter,我们改为使用标记特征 NotU32 作为 impl<L: NotU32> GetU32 for Pair<L, u32> { ... } 来定义它。这改变了问题:我们现在必须为除 u32 之外的所有类型设置此标记特征。这是 auto_trait(为所有类型添加特征)和 negative_impl(从某些类型中删除它)进来的。

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