1 个不稳定版本
0.1.0 | 2021年10月22日 |
---|
#725 在 过程宏
24KB
422 行
opimps
opimps 简化了Rust的运算符重载,使其可以以类似于C++的方式编写,但无需重复代码。
总结
在Rust中重载运算符时,我们可能会遇到设计问题,即数据应该是 borrowed
或 owned
。在许多情况下,我们并不关心这一点,应该由运算符的调用者来决定使用什么。
在下面的示例中,我们重载了二元运算符 +
,以便计算两个车库中的汽车总数。
想象我们有一个车库,里面存放了一些汽车。
struct Garage {
number_of_cars: u64
}
使用 opimps
,我们可以重载运算符,以便执行类似在两个车库之间添加汽车数量这样的操作。
use core::ops::Add;
#[opimps::impl_ops(Add)]
fn add(self: Garage, rhs: Garage) -> u64 {
self.number_of_cars + rhs.number_of_cars
}
在幕后,代码会生成以下代码,如果我们想要允许所有权和借用数据的组合,我们原本需要手动实现这些代码。
use core::ops::Add;
struct Garage {
number_of_cars: u64
}
impl Add for Garage {
type Output = u64;
fn add(self, rhs: Garage) j-> Self::Output {
self.number_of_cars + rhs.number_of_cars
}
}
impl Add for &Garage {
type Output = u64;
fn add(self, rhs: Garage) j-> Self::Output {
self.number_of_cars + rhs.number_of_cars
}
}
impl Add<&Garage> for Garage {
type Output = u64;
fn add(self, rhs: &Garage) j-> Self::Output {
self.number_of_cars + rhs.number_of_cars
}
}
impl Add<&Garage> for &Garage {
type Output = u64;
fn add(self, rhs: &Garage) j-> Self::Output {
self.number_of_cars + rhs.number_of_cars
}
}
注意,在生成的代码中,有 4 个实现来表示在 Garages
中添加汽车数量时所有可能的使用情况,而在所有这些情况下,函数体都是相同的。这是由于Rust能够自动确定访问结构体成员所需传播的级别,与C++不同,在C++中我们需要具体指定,并使用解引用、点操作符和/或箭头操作符的组合,具体取决于输入是否是引用对象。
现在,我们可以以任何顺序使用运算符来处理 borrowed
和/或 owned
数据。
let garage_a = Garage { number_of_cars: 4 };
let garage_b = Garage { number_of_cars: 9 };
let total = garage_a + garage_b;
let total = &garage_a + garage_b;
let total = garage_a + &garage_b;
let total = &garage_a + &garage_b;
[注意!] 请记住Rust的隐藏 move 语义,并且如果尝试同时进行所有
total
赋值,代码将无法编译。非引用数据在调用时就会被移出作用域,并且将不再在原始创建的作用域中可用。
对于熟悉C++11及更高版本的读者,可以阅读以下链接了解更多信息:这里。
用法
impl_op
在总结中,我们介绍了impl_ops
,这是一个生成借用数据和所有数据代码的宏。impl_op
(注意末尾缺少's)是一种以常规方式重载运算符而不为借用数据生成变体的一种方式。
#[opimps::impl_op(Add)]
fn add(self: Garage, rhs: Garage) -> u64 {
self.number_of_cars + rhs.number_of_cars
}
这生成以下1:1的实现。
impl Add for Garage {
type Output = u64;
fn add(self, rhs: Garage) -> u64 {
self.number_of_cars + rhs.number_of_cars
}
}
这意味着我们只能这样做,不能做更多。
let garage_a = Garage { number_of_cars: 4 };
let garage_b = Garage { number_of_cars: 9 };
let total = garage_a + garage_b;
assert_eq!(13, total);
/* Neither of the three lines below will work! */
// let total = &garage_a + garage_b;
// let total = garage_a + &garage_b;
// let total = &garage_a + &garage_b;
与我们在总结示例中展示的impl_ops
相比,这本身并不太有用,但它允许我们根据我们的设计选择对实现进行微调。
如果我们想要重载运算符,其中只有运算符的左侧是借用类型,那么我们可以按照以下方式实现。
#[opimps::impl_op(Add)]
fn add(self: &Garage, rhs: Garage) -> u64 {
self.number_of_cars + rhs.number_of_cars
}
这生成了以下内容。
impl Add for &Garage {
type Output = u64;
fn add(self, rhs: Garage) -> u64 {
self.number_of_cars + rhs.number_of_cars
}
}
现在我们可以做&garage_a + garage_b
。
let garage_a = Garage { number_of_cars: 4 };
let garage_b = Garage { number_of_cars: 9 };
let total = &garage_a + garage_b;
assert_eq!(13, total);
/* Neither of the three lines below will work! */
// let total = garage_a + garage_b;
// let total = garage_a + &garage_b;
// let total = &garage_a + &garage_b;
同样,我们可以使用impl_op
或其他不同类型来组合借用数据。
// borrowed right hand side
fn add(self: Garage, rhs: &Garage);
// borrowed both sides
fn add(self: &Garage, rhs: &Garage)
// Using a different type so that we can do something like `garage_a + 2`
fn add(self: Garage, rhs: u64)
impl_ops
impl_ops
在底层使用impl_op
来生成借用和所有数据组合的二进制运算符的实现。
use core::ops::Mul;
struct A;
struct B;
struct C;
#[opimps::impl_ops(Mul)]
fn mul(self: A, rhs: B) -> C { ... }
以上将生成以下内容。
impl Mul<B> for A { type Output = C; ... }
impl Mul<B> for &A { type Output = C; ... }
impl Mul<&B> for A { type Output = C; ... }
impl Mul<&B> for &A { type Output = C; ... }
impl_ops_lprim和impl_ops_rprim
有些情况下,我们想要为借用数据生成代码,但其中一个元素是原始类型。如果我们使用impl_ops
,这可能会引起问题。因此,创建了impl_ops_lprim
和impl_ops_rprim
来解决这个问题;分别代表左侧原始类型和右侧原始类型。
impl_ops_lprim
#[opimps::impl_ops_lprim]
fn add(self: u64, rhs: Garage) -> u64 {
...
}
impl_ops_rprim
#[opimps::impl_ops_lprim]
fn add(self: Garage, rhs: u64) -> u64 {
...
}
impl_uni_op
impl_op
实现二进制运算符,而impl_uni_op
实现一元运算符。
struct Person {
has_cars: bool
}
#[opimps::impl_uni_op(core::ops::Not)]
fn not(self: Person) -> Person {
Person { has_cars: !self.has_cars }
}
impl_uni_ops
与impl_ops
生成二进制运算符的借用和所有数据实现类似,impl_uni_ops
生成一元运算符的借用和所有数据实现。在底层,impl_uni_ops
的实现使用impl_uni_op
。
给定以下struct
struct Person {
has_cars: bool
}
可以为Person
实现一元运算符!
,如下所示
use core::ops::Not;
#[opimps::impl_uni_ops(Not)]
fn not(self: Person) -> Person {
Person { has_cars: !self.has_cars }
}
现在我们应该能够做以下操作
let a = Person { has_cars: true };
let res = !(&a);
let res = !a;
impl_op_assign
我们可以实现基于赋值的运算符,如+=
、*=
、-=
。
pub struct TestObj {
pub val: i32
}
#[opimps::impl_ops_assign(std::ops::AddAssign)]
fn add_assign(self: TestObj, rhs: TestObj) {
self.val += rhs.val;
}
let mut a = TestObj { val: 4 };
let b = TestObj { val: 7 };
a += b;
assert_eq!(11, a.val);
let mut a = TestObj { val: 4 };
let b = TestObj { val: 7 };
a += &b;
assert_eq!(11, a.val);
assert_eq!(7, b.val);
泛型
我们可以像使用标准函数的泛型一样为impl_ops
和impl_uni_ops
使用泛型。
use std::ops::Add;
pub struct Num<T>(pub T);
/// ```
/// use opimps::impl_ops;
/// use mycrate::Num;
///
/// let a = Num(2.0);
/// let b = Num(3.0);
///
/// let res = a + b;
/// assert_eq!(5.0, res.0);
/// ```
#[opimps::impl_ops(Add)]
fn add<T>(self: Num<T>, rhs: Num<T>) -> Num<T> where T: Add<Output = T> + Copy {
Num(self.0 + rhs.0)
}
实际示例
到目前为止,我们只展示了无用的示例,但这是因为这些示例被简化了,以便于理解。以下是一个示例,它使用了SIMD
指令来计算x86_64架构的四元数乘法。虽然这不是完整的源代码,但这只是一个片段,展示了如何使用opimps
实现数学库。
// No explanation of quaternions will be provided here since it involves a lot of theory. You only need to know that it's used to perform 3D rotations while avoiding the issues of gimbal locking that occurs from performing rotations using euler angles.
/// ```
/// use noname::v32::quat::Quat;
/// let l = Quat::<f32>::new(7.0, 1.0, 9.0, 4.0);
/// let mut r = Quat::<f32>::new(9.0, 4.0, 8.0, 2.0);
///
/// let res = &l * &r;
/// r.j = 5.0; r.k = 7.0;
///
/// let r = Quat::<f32>::new(9.0, 5.0, 7.0, 2.0);
///
/// let res = Quat::from(res);
///
/// assert_eq!( 22.0, res.i);
/// assert_eq!( 43.0, res.j);
/// assert_eq!( 69.0, res.k);
/// assert_eq!(-131.0, res.s);
///
/// let res = l * r;
/// let res = Quat::from(res);
///
/// assert_eq!( 12.0, res.i);
/// assert_eq!( 54.0, res.j);
/// assert_eq!( 72.0, res.k);
/// assert_eq!(-123.0, res.s);
/// ```
#[opimps::impl_ops(Mul)]
fn mul(self: Quat<i32>, rhs: Quat<i32>) -> Computable {
let l = self.as_slice();
let r = rhs.as_slice();
let s = (&self.s * &rhs.s) - (&self).dot(rhs.clone());
let v1 = Computable::from(l);
let v2 = Computable::from(r);
let s1 = Computable::all((&self).s);
let s2 = Computable::all((&rhs).s);
let s1v2 = s1 * v2;
let s2v1 = s2 * v1;
let v1xv2 = self.cross(rhs);
let mut res = s1v2 + s2v1 + v1xv2;
unsafe { crate::insert_i32!(res, s, 3) };
return res;
}
依赖关系
~1.5MB
~35K SLoC