Handling Associated Output Types in Generated Code

TLDR: When generating code for ...Assign traits in std::ops with operands known to produce inconsistent-but-valid output types, how ought one to handle the output types to make it the least confusing?


A nice macro generates a general representation of a number. This representation includes coefficients of some numeric type T corresponding to the defining elements of some vector basis:

struct Number<T>(Vec<T>);

The macro also generates some Special utility types – essentially like sparse vectors on predetermined useful bases – like:

/// A special type with an a-coefficient
struct A<T>{ a: T }
/// A special type with a c-coefficient
struct C<T>{  c: T }
/// A special type with a d-coefficient
struct D<T>{  d: T }
/// A special type with a- and b-coefficients
struct AB<T>{  a: T, b: T }
/// A special type with a-, b- and c-coefficients
struct ABC<T>{   a: T, b: T, c: T }
// ..et. cetera

The nice macro also generates std::ops::Sub and std::ops::Add trait implementations by determining the associated Output types according to two rules:

1. When an in-basis coefficient is in the right-hand-side, the result is an instance of the left-hand-side specialized representation type:

AB<T> - A<T> = AB<T> // Basis: {a, b} + {a} = {a, b} ... can use same type

2. When an out-of-basis coefficient is in the right-hand-side, the result is a general representation type unless a known specialized type is exists matching the new basis:

AB<T> - C<T> = ABC<T> 
// ^ Basis: {a,b} + {c} = {a,b,c} (basis matches another type)

AB<T> + D<T> = Number<T>
// ^ Basis: {a,b} + {d} = {a,b,c,d,...} (no basis-match, so use general type)

The Assign Problem

What should the nice macro do with the AddAssign and SubAssign traits?!

The solution above (using the general type for out-of-bounds basis operations) doesn't really work – obviously it can't change the type of the self being assigned to.

For now, it uses a nan-producing function to signify something wrong, but then, the basis behavior seems inconsistent:

AB<T> += D<T> -> AB::<T>::nan() // Basis: {a,b} += {d} ≠ {a,b}

Would it be less confusing to just not generate the ...Assign traits for these cases at all? I feel like this is a cop-out somehow. Or does this nan result help the user know they're writing code that attempts invalid operations?

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.