Crate gen_ops

source ·
Expand description

Macros for operator overloading of generic types.

Usage

The macros need four statements

  1. (Optional) Generic parameter names (See)
  2. Type signature or extended type signature(See)
  3. Callable expressions for each operator, and optionally, a where clause to be applied for the impl(See)
  4. (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

MacroType sign.Lhs type sign.Rhs type sign.Lhs = Rhs
gen_ops!borrowedL, &L, &mut LR, &R, &mut Rallowed
gen_ops_ex!extendedL, ref L, mut LR, ref R, mut Rallowed
gen_ops_comm!borrowedL, &L, &mut LR, &R, &mut Rnot allowed
gen_ops_comm_ex!extendedL, ref L, mut LR, ref R, mut Rnot allowed

Assignment

MacroType sign.Lhs type sign.Rhs type sign.
gen_ops!borrowedL, &mut LR, &mut R
gen_ops_ex!extendedL, mut LR, mut R

Unary

MacroType sign.Lhs type sign.
gen_ops!borrowedL, &L, &mut L
gen_ops_ex!extendedL, ref L, mut L

Closure/function

OperatorLhsRhsReturn
binary&Lhs&RhsOut
assign&mut Lhs&Rhs()
unary&Lhs-Out

Macros