在具有冲突的特征实现中选择首选实现(使用负界限)

9

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

操场链接

我有一个包含两个字段的结构体。如果这两个字段中有一个(或两个)是 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>`

我应该如何通过选择首选实现来解决这个冲突? 我可以使用夜间功能,例如特化。
我尝试过显式定义冲突的情况(注释代码),但只会导致更多的冲突。我追求的另一个解决方案是尝试使用特化,但我无法将其应用于我的用例。另一个解决方案是将第二个实现指定为 impl<L: !u32> GetU32 for Pair<L, u32>(仅对特征进行定义,其中唯一的u32.right),但负边界不存在。
我知道有其他关于冲突特征实现的问题,但在这种简单情况下无法选择首选实现的问题上,我没有找到类似的问题。

编辑 我想扩展我的问题,以便更好地解释我的真正问题和我目前使用的解决方案。

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

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

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 }
}

我认为目前还没有支持管理此功能的任何特性。也许TypeId在你的情况下会有用。 - Mihir Luthra
一些与您的要求相关的问题:负特征边界 —— 从泛型中排除类型 —— 特化 - Mihir Luthra
2个回答

9

正如问题所提到的,可以使用负界限来解决这个情况。尽管这个功能目前连在夜间版本中都不可用。

幸运的是,有一个变通方法来通过组合两个现有的夜间特性实现足够形式的负界限: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()));
}

Playground链接

由于我们不能使用impl<L: !u32> GetU32 for Pair<L, u32> { ... }(否定的类型限制),因此我们改用标记trait NotU32 定义次要的getter,如下所示:impl<L: NotU32> GetU32 for Pair<L, u32> { ... }。这样就移动了问题:现在我们必须为除u32之外的所有类型设置此标记trait。这就是auto_trait(为所有类型添加一个trait)和negative_impl(从某些类型中删除它)发挥作用的地方。

这个答案的限制是,今天你无法使用这种方法定义通用的Not<T> trait。


1

一个避免这个问题的简单方法是只实现你需要的类型,而不是使用泛型。

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);

1
这是我目前正在使用的解决方案:由于!u32不存在,我为我关心的每种非u32类型实现它。我正在寻找一种避免显式指定类型的方法。 - Demurgos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接