Expand description
Macros for operator overloading of generic types.
Usage
The macros need four statements
- (Optional) Generic parameter names (See)
- Type signature or extended type signature(See)
- Callable expressions for each operator, and optionally, a where clause to be applied for the impl(See)
- (Optional) Where clause to be applied for all impls(See)
Note: All statements end with a semicolon except the where clause.
Example
#[derive(Debug, Copy, Clone, PartialEq)]
struct Pair<T>(pub T, pub T);
#[inline]
fn sub_pair<T>(a: &Pair<T>, b: &Pair<T>) -> Pair<T>
where T: Sub<Output=T> + Copy {
Pair(a.0 - b.0, a.1 - b.1)
}
gen_ops!(
<T>; // Generic parameter names
types Pair<T>, Pair<T> => Pair<T>; // Type signature
for + call |a: &Pair<T>, b: &Pair<T>| {
Pair(a.0 + b.0, a.1 + b.1)
};
for - call sub_pair; // Callable expressions for operators
where T: Add<Output=T> + Sub<Output=T> + Copy
);
let a = Pair(2, 3);
let b = Pair(1, 8);
println!("a + b = {:?}", a + b); //a + b = Pair(3, 11)
println!("a - b = {:?}", a - b); //a - b = Pair(1, -5)
1. Generic parameter names
First statement is Genetic parameters for impl
.
gen_ops!(
<T, U, V>;
.....
);
// results in
impl<T, U, V> .... {
....
}
Lifetime params
Lifetime params must be added at the beginning.
gen_ops!(
<'a, 'b, T, U, V>;
.....
);
// results in
impl<'a, 'b, T, U, V> .... {
....
}
Const params
To add const params add a |
followed by the const parameters. The const params must be at the end
and |
is mandatory even if the const params are the only parameters.
gen_ops!(
<T, U, V | const SZ:usize, const DEF: i32>;
.....
);
// results in
impl<T, U, V, const SZ: usize, const DEF: i32> .... {
....
}
//and
gen_ops!(
<| const SZ:usize, const DEF: i32>; // `|` is mandatory
.....
);
// results in
impl<const SZ: usize, const DEF: i32> .... {
....
}
Constraints
Type constraints are not supported. Use where clause instead.
2. Type signature or extended type signature
Type signatures are used by gen_ops!
and gen_ops_comm!
.
And extended type signatures are used by gen_ops_ex!
and gen_ops_comm_ex!
.
Syntax
- For binary operators the signature is written as
types Lhs, Rhs => Out;
. - For assignment operators the signature is written as
types Lhs, Rhs;
. - For unary operators the signature is written as
types Lhs => Out;
.
Type signatures can have borrowed types for Lhs
and Rhs
, e.g, types &Lhs, &mut Rhs => Out;
.
What is extended type signature?
Extended type signatures, however, cannot use borrowed types.
The extended types signatures can have ref
or mut
modifiers for Lhs
and Rhs
.
ref Lhs
implies that the trait is implemented for both & Lhs
and Lhs
.
mut Lhs
implies that the trait is implemented for &mut Lhs
, &Lhs
and Lhs
.
For example, types ref T, ref T;
will implement types &T, &T;
, types &T, T;
,
types T, &T;
and types T, T
.
3. Callable expressions for each operator
Callable expression statements are written as for <operator> call <expr>;
The expression can be a closure or a function.
The closure or function must take immutable borrowed parameters except for assignment operators where the first parameter must be mutable.
Example
struct A(i32);
struct B(f32);
struct C(f64);
fn sub_abc(a:&A, b:&B) -> C {
C(b.0 as f64 - a.0 as f64)
}
gen_ops!(
types A, B => C;
for + call |a: &A, b: &B| C(b.0 as f64 + a.0 as f64);
for - call sub_abc;
);
Doc Strings
You can add doc string above each for
statements.
gen_ops!(
...
/// Docs here
/// Docs here
for + call ....
/// Docs here
for - call ...
)
Where clause for each operator
Optionally you can add where clause to be used only with one operator. Add this statement after the semicolon and enclosed in parentheses.
Example
struct Complex<T>(T, T);
gen_ops_ex!(
<T>;
types ref Complex<T>, ref Complex<T> => Complex<T>;
for + call |a: &Complex<T>, b: &Complex<T>| Complex(a.0+b.0, a.1+b.1);
(where T: Add<T, Output=T>) //applies to + impls only
for - call |a: &Complex<T>, b: &Complex<T>| Complex(a.0-b.0, a.1-b.1);
(where T: Sub<T, Output=T>) //applies to - impls only
where T: Copy //applied to all impls
);
Also note that there is no need for semicolon after the individual where clauses.
4. Where clause for all impls
Optionally you can add a where clause as the last statement. This will be applied to all generated impls. Note that the where clause does not end with a semicolon.
gen_ops!(
<T>;
...
where T: Copy
);
// results in
impl<T> ... where T: Copy {
...
}
Why use/not use gen_ops crate?
Features
- Multiple operators can be implemented with one macro call.
- Use closure or function for the implementation
- Supports generics
Limitations
- These macros use TT munching which slows down compilation.
Quick Reference Tables
Binary
Macro | Type sign. | Lhs type sign. | Rhs type sign. | Lhs = Rhs |
---|---|---|---|---|
gen_ops! | borrowed | L , &L , &mut L | R , &R , &mut R | allowed |
gen_ops_ex! | extended | L , ref L , mut L | R , ref R , mut R | allowed |
gen_ops_comm! | borrowed | L , &L , &mut L | R , &R , &mut R | not allowed |
gen_ops_comm_ex! | extended | L , ref L , mut L | R , ref R , mut R | not allowed |
Assignment
Macro | Type sign. | Lhs type sign. | Rhs type sign. |
---|---|---|---|
gen_ops! | borrowed | L , &mut L | R , &mut R |
gen_ops_ex! | extended | L , mut L | R , mut R |
Unary
Macro | Type sign. | Lhs type sign. |
---|---|---|
gen_ops! | borrowed | L , &L , &mut L |
gen_ops_ex! | extended | L , ref L , mut L |
Closure/function
Operator | Lhs | Rhs | Return |
---|---|---|---|
binary | &Lhs | &Rhs | Out |
assign | &mut Lhs | &Rhs | () |
unary | &Lhs | - | Out |
Macros
- The primary macro for all operators.
- Implements commutative operations.
- Implements commutative operations for borrowed types.
- Implements trait for borrowed types.