无法为整数类型实现泛型 FN

cannot implement generic fn for integer types

提问人:user52366 提问时间:11/4/2023 更新时间:11/5/2023 访问量:56

问:

我在 C++ 方面经验丰富,并开始玩 rust。

尝试实现一些简单的泛型函数时,我遇到了以下问题:

use std::ops::BitAnd;
use std::cmp::Eq;

fn is_odd_i32(x: u32) -> bool {
    if x & 1_u32 == 1_u32 { true } else { false }
}

fn is_odd<T: BitAnd + Eq>(x: &T) -> bool {
    if (*x & (1 as T)) == (1 as T) { true } else { false }
}

fn main() {
    println!("is_odd -> '{}'", is_odd(&23_u64));
    println!("is_odd -> '{}'", is_odd(&23_u32));
}

问题似乎是将按位和结果与 0 或 1 进行比较。我知道要做到这一点,1(或 0)必须可转换为 T 型,但不知道如何实现这一点。我也试过了,但这也没有用。T::try_from(1_u8).ok().unwrap()

我不明白如何解决这个问题......

我得到的错误是:

error[E0369]: binary operation `==` cannot be applied to type `<T as BitAnd>::Output`
  --> src/main.rs:27:24
   |
27 |     if (*x & (1 as T)) == (1 as T) { true } else { false }
   |        --------------- ^^ -------- T
   |        |
   |        <T as BitAnd>::Output
   |
help: consider further restricting the associated type
   |
26 | fn is_odd<T: BitAnd + Eq>(x: &T) -> bool where <T as BitAnd>::Output: PartialEq<T> {
   |                                          +++++++++++++++++++++++++++++++++++++++++

error[E0605]: non-primitive cast: `{integer}` as `T`
  --> src/main.rs:27:14
   |
27 |     if (*x & (1 as T)) == (1 as T) { true } else { false }
   |              ^^^^^^^^ an `as` expression can only be used to convert between primitive types or to coerce to a specific trait object

error[E0605]: non-primitive cast: `{integer}` as `T`
  --> src/main.rs:27:27
   |
27 |     if (*x & (1 as T)) == (1 as T) { true } else { false }
   |                           ^^^^^^^^ an `as` expression can only be used to convert between primitive types or to coerce to a specific trait object

顺便说一句,我只是在玩特征和泛型,这不是测试整数是否奇数的最佳方法。

仿制药 蚀性状

评论


答:

6赞 Masklinn 11/4/2023 #1

代码无法工作的主要原因是因为 Rust 的泛型与 C++ 的模板完全不同的工作原理。

在 Rust 的泛型中,约束是你唯一可以依赖的东西。所以当你写的时候,这就是能力的范围。这里甚至没有任何东西是数字相邻的,因为据你所知,有人为了方便起见而在哈希集上实现。因此,你的强制转换对编译器来说完全没有意义(强制转换首先不是泛型操作),因此它的抱怨。T: BitAnd + EqTT&

要使用泛型,你需要一种通用的方式来表达这些操作,比如“数字塔”,它将所有基本数字操作定义为泛型,但早在早期,核心团队就认为这不是一个对核心语言和标准库有用的努力,过去几乎没有的东西被删除/移动到 num 生态系统中。为此,特别是 One 特征:

fn is_odd<T: BitAnd + Eq + One>(x: &T) -> bool {
    *x & T::one() == T::one()
}

(我删除了无用的语法)

当然,如果你应用它,你会发现更多的问题:

  • BitAnd 不保证其输出与其输入有任何关系,因此您需要将其限制在有意义的内容中,一种选择是仅要求返回:T & TT

    fn is_odd<T: BitAnd<Output=T> + Eq + One>(x: &T) -> bool {
        *x & T::one() == T::one()
    }
    
  • 你不能把东西从引用中移出,这正是这里试图做的事情。您可以限制类型(允许复制引用),或者只是不进行引用,我选择了后者,因为我不明白重点:*xxCopyx

    fn is_odd<T: BitAnd<Output=T> + Eq + One>(x: T) -> bool {
        x & T::one() == T::one()
    }
    

好了,如果你修复调用站点以删除引用,它现在可以工作了。 现在可以像你想要的那样灵活地工作(如果一个类型碰巧以一种没有意义的方式实现所有三个操作,这可能是完全不连贯的,这可能就是为什么is_odd作为 Integer 特征的非默认操作的原因)。is_oddnum

顺便说一句,在这种情况下,您的 println 是过于复杂的 dbg 版本:

    dbg!(is_odd(23_u64));

=>

[src/main.rs:9] is_odd(23_u64) = true

顺便说一句,您也可以使用转换版本(不使用 ),但是在这种情况下,如最初所述,您需要告诉编译器您需要确切的转换,这里需要从 u8 转换,又名 .在这种情况下,此替代版本也有效numTT: From<u8>

fn is_odd<T: BitAnd<Output=T> + Eq + From<u8>>(x: T) -> bool {
    x & 1.into() == 1.into()
}

例如,如果您尝试运行它,则不会(因为您可以将 U8 转换为 i16,但不能转换为 i8,因为一半的范围无效)。T: i8

对于这个特定的极端情况,你可以通过类似的设置来代替。然后使用 ,或者只是在绑定错误时:TryFromT: TryFrom<u8>ok().unwrap()1.try_into().unwrap()

fn is_odd<T>(x: T) -> bool
where
    T: BitAnd<Output = T> + Eq + TryFrom<u8>,
    <T as TryFrom<u8>>::Error: std::fmt::Debug,
{
    x & 1.try_into().unwrap() == 1.try_into().unwrap()
}