#copy #reference #generic #macro #forward #operations #type

forward_ref_generic

为Copy类型操作提供泛型前向引用

8个版本

0.2.1 2022年2月12日
0.2.0 2022年2月12日
0.1.5 2022年2月10日

1964 in Rust模式

Download history 24/week @ 2024-03-31 3/week @ 2024-04-07 37/week @ 2024-04-14 4/week @ 2024-04-21 8/week @ 2024-04-28 1/week @ 2024-05-19 7/week @ 2024-06-02 6/week @ 2024-06-09 5/week @ 2024-06-16 28/week @ 2024-06-30 24/week @ 2024-07-14

每月52次下载

MIT许可证

40KB
276

forward_ref_generic

Crates.io docs.rs GitHub last commit License

这个crate作为forward_ref的独立扩展,可选择支持泛型。

forward_ref_*宏是Rust核心库中使用的宏,用于更轻松地实现原始类型的操作。当为一个类型T实现操作op(如AddMul)时,std::ops文档建议不仅为T op T实现操作,还要为T op &T&T op T&T op &T实现操作。实际上,这些变体的实现对于Copy类型来说相当简单且繁琐。由于这些简单的实现对于所有Copy类型基本上是相同的,因此可以使用forward_ref_*宏来自动实现它们。

已经存在一些解决方案(其中之一是前面提到的forward_ref crate,以及impl_ops),然而它们中没有一个(或者至少我没有找到的)支持泛型类型。也就是说,如果一个类型像Point<T> {x: T, y: T},到目前为止,仍然需要手动实现所有变体。这个crate提供了也支持泛型类型的宏,包括特质界限,所以唯一的假设是操作实现的类型是Copy

为不同的操作类型提供了独立的宏

使用方法

将以下内容添加到您的 Cargo.toml

[dependencies]
forward_ref_generic = "0.2"

有关宏的使用方法,请参阅文档或查看下面的示例。

示例

std::opsPoint 示例

让我们使用 std::opsPoint 示例来了解通常如何实现它,以及如何使用 forward_ref_* 宏来完成

use std::ops::Add;

#[derive(Debug, Copy, Clone, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

impl Add for Point {
    type Output = Self;

    fn add(self, rhs: Self) -> Self::Output {
        Self {x: self.x + rhs.x, y: self.y + rhs.y}
    }
}

此时,可以将两个点相加

let p1 = Point { x: 3, y: 3 };
let p2 = Point { x: 5, y: 2 };
assert_eq!(p1 + p2, Point { x: 8, y: 5 });

但是,使用引用上的运算符将无法编译

let p3 = &p2;
assert_eq!(p1 + &p2, Point { x: 8, y: 5 }); // ✖ does not compile
assert_eq!(p1 + p3, Point { x: 8, y: 5 }); // ✖ does not compile
assert_eq!(p1 + *p3, Point { x: 8, y: 5 }); // ✔ compiles

为了解决这个问题,需要为 Point 实现 Add<&Point>

impl Add<&Point> for Point {
    type Output = Self;

    fn add(self, rhs: &Self) -> Self::Output {
        Self::add(self, *rhs)
    }
}

let p3 = &p2;
assert_eq!(p1 + &p2, Point { x: 8, y: 5 });
assert_eq!(p1 + p3, Point { x: 8, y: 5 });

现在我们还需要为 &Point + Point&Point + &Point 添加实现。但这非常冗长且令人厌烦。相反,我们可以使用 forward_ref_binop

use forward_ref_generic::forward_ref_binop;

forward_ref_binop! {
    impl Add for Point
}

let p1 = Point { x: 3, y: 3 };
let p2 = Point { x: 5, y: 2 };
assert_eq!(p1 + p2, Point { x: 8, y: 5 });
assert_eq!(p1 + &p2, Point { x: 8, y: 5 });
assert_eq!(&p1 + p2, Point { x: 8, y: 5 });
assert_eq!(&p1 + &p2, Point { x: 8, y: 5 });

泛型支持

让我们泛化我们的 Point 结构体,使其支持任何类型的成员。在这种情况下,我们仍然可以使用 forward_ref_binop,但我们需要告诉宏我们使用了哪些泛型。我们还需要告诉它所有所需的特质约束。请注意,由于技术原因,我们需要在泛型列表周围添加尖括号 []

use std::ops::Add;
use forward_ref_generic::forward_ref_binop;

#[derive(Debug, Copy, Clone, PartialEq)]
struct Point<T> {
    x: T,
    y: T,
}

impl<T> Add for Point<T>
where
    T: Copy + Add<Output = T>,
{
    type Output = Self;

    fn add(self, rhs: Self) -> Self::Output {
        Self {x: self.x + rhs.x, y: self.y + rhs.y}
    }
}

// for the exact syntax required by each macro, refer to the macro's documentation page
forward_ref_binop! {
    [T]
    impl Add for Point<T>
    where T: Copy + Add<Output = T>
}

let p1 = Point { x: 3, y: 3 };
let p2 = Point { x: 5, y: 2 };
assert_eq!(p1 + p2, Point { x: 8, y: 5 });
assert_eq!(p1 + &p2, Point { x: 8, y: 5 });
assert_eq!(&p1 + p2, Point { x: 8, y: 5 });
assert_eq!(&p1 + &p2, Point { x: 8, y: 5 });

常量泛型和不同的右侧

到目前为止,操作的右侧与左侧具有相同的类型。但 forward_ref_* 宏也可选地支持定义不同的右侧。为此,只需像这样在左侧类型之后立即添加右侧类型即可

forward_ref_binop! {
    [generics...]
    impl OP for LHS, RHS
    where ...
}

为了演示这一点,我们将使用泛型 Stack-Matrix 并在它上面实现 Mul

use std::ops::{Add, Mul};
use forward_ref_generic::forward_ref_binop;

#[derive(Debug, Copy, Clone, PartialEq)]
struct Matrix<T, const M: usize, const N: usize> {
    m: [[T; N]; M],
}

impl<T, const M: usize, const N: usize, const L: usize> Mul<Matrix<T, N, L>> for Matrix<T, M, N>
where
    T: Copy + Add<Output = T> + Mul<Output = T>,
{
    type Output = Matrix<T, M, L>;

    fn mul(self, rhs: Matrix<T, N, L>) -> Self::Output {
        // ...
    }
}

forward_ref_binop! {
    [T, const M: usize, const N: usize, const L: usize]
    impl Mul for Matrix<T, M, N>, Matrix<T, N, L>
    where T: Copy + Add<Output = T> + Mul<Output = T>
}

let m1 = Matrix {m: [[1, 2, 2], [2, 1, 2]]};
let m2 = Matrix {m: [[0, 1], [1, 1], [2, 1]]};

assert_eq!(m1 * m2, Matrix {m: [[6, 5], [5, 5]]});
assert_eq!(m1 * &m2, Matrix {m: [[6, 5], [5, 5]]});
assert_eq!(&m1 * m2, Matrix {m: [[6, 5], [5, 5]]});
assert_eq!(&m1 * &m2, Matrix {m: [[6, 5], [5, 5]]});

自定义运算符

请注意,在所有之前的示例中,宏在 哪个 操作上需要实现的所有信息都是特质名称。这是通过具体检查已知的操作特质并在宏内部插入所需方法名称来实现的。目前这 适用于标准数学运算符(即不适用于位运算符和自定义运算符)。然而,仍然可以使用这些宏,但在这种情况下必须指定方法名称。如果 LHS = RHS,则可以省略 RHS。

forward_ref_binop! {
    [generics...]
    impl OP, METHOD for LHS, RHS
    where ...
}

为了演示,我们将实现 Not 一元运算符在 std::ops::Not 的文档中的 Answer 示例上。

use std::ops::Not;
use forward_ref_generic::forward_ref_unop;

// notice we have to add the `Copy` trait, as otherwise the macro will not work correctly
#[derive(Debug, Copy, Clone, PartialEq)]
enum Answer {
    Yes,
    No,
}

impl Not for Answer {
    type Output = Self;

    fn not(self) -> Self::Output {
        match self {
            Answer::Yes => Answer::No,
            Answer::No => Answer::Yes,
        }
    }
}

// this time we use the macro for unary operators and specify the `not` method's name
forward_ref_unop! {
    impl Not, not for Answer
}

assert_eq!(!Answer::Yes, Answer::No);
assert_eq!(!Answer::No, Answer::Yes);

assert_eq!(!&Answer::Yes, Answer::No);
assert_eq!(!&Answer::No, Answer::Yes);

使操作具有交换律

还有宏可以自动使操作具有交换律。也就是说,对于两个类型 TU,如果 T binop U 已经实现,则可以使用 commutative_binop 自动实现 U binop T。如果 TU 此外是 Copy,则 T binop &U&T binop U&T binop &UU binop &T&U binop T&U binop &T 可以使用 forward_ref_commutative_binop 自动实现。

use std::ops::Add;
use forward_ref_generic::{commutative_binop, forward_ref_commutative_binop};

// two wrappers for integers
#[derive(Clone, Copy, PartialEq)]
struct Int1(i32);

#[derive(Clone, Copy, PartialEq)]
struct Int2(i32);

impl Add<Int2> for Int1 {
    type Output = i32;

    fn add(self, rhs: Int2) -> Self::Output {
        self.0 + rhs.0
    }
}

// note that the order of `LHS` and `RHS` is that
// of the original operation's implementation
// not that of the created one
commutative_binop! {
    impl Add for Int1, Int2
}

// the order of `LHS` and `RHS` here doesn't matter
// as `LHS binop RHS` and `RHS binop LHS` are both required anyway
forward_ref_commutative_binop! {
    impl Add for Int1, Int2
}

let i1 = Int1(5);
let i2 = Int2(3);

assert_eq!(i1 + i2, 8);
assert_eq!(i2 + i1, 8);

assert_eq!(&i1 + i2, 8);
assert_eq!(i1 + &i2, 8);
assert_eq!(&i1 + &i2, 8);

assert_eq!(&i2 + i1, 8);
assert_eq!(i2 + &i1, 8);
assert_eq!(&i2 + &i1, 8);

无运行时依赖