8个版本
0.2.1 | 2022年2月12日 |
---|---|
0.2.0 | 2022年2月12日 |
0.1.5 | 2022年2月10日 |
1964 in Rust模式
每月52次下载
40KB
276 行
forward_ref_generic
这个crate作为forward_ref的独立扩展,可选择支持泛型。
forward_ref_*
宏是Rust核心库中使用的宏,用于更轻松地实现原始类型的操作。当为一个类型T
实现操作op
(如Add
或Mul
)时,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
。
为不同的操作类型提供了独立的宏
- 一元运算符,例如
Neg
:forward_ref_unop
- 二元运算符,例如
Add
:forward_ref_binop
- 赋值运算符,例如
AddAssign
:forward_ref_op_assign
使用方法
将以下内容添加到您的 Cargo.toml
[dependencies]
forward_ref_generic = "0.2"
有关宏的使用方法,请参阅文档或查看下面的示例。
示例
std::ops
的 Point
示例
让我们使用 std::ops
的 Point
示例来了解通常如何实现它,以及如何使用 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);
使操作具有交换律
还有宏可以自动使操作具有交换律。也就是说,对于两个类型 T
和 U
,如果 T binop U
已经实现,则可以使用 commutative_binop
自动实现 U binop T
。如果 T
和 U
此外是 Copy
,则 T binop &U
、&T binop U
、&T binop &U
、U 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);