Default for struct with generic types

Hello,
I want to implement default method for a struct with field that can be changed between some types that implement the same trait.
This is my code.
Can anyone help this?

Then you can eliminate the generic and use the either crate or something similar

But if I want to make more than one operation then it will not work.

Using dynamic dispatch, you can get unlimited operations, but it requires indirection and a performance cost.

1 Like

Hmm what about a struct like this:

struct Struct<L: Operation, R:Operation>

and then you contain:

struct Struct<L: Operation, R:Operation>
{
    name: String,
    operation: Either<L, R>
}
1 Like

This could work, because you can then implement Operation for Either<L, R> where L: Operation, R: Operation, and so you could have an arbitrary number of operations.

1 Like

This "problem" is very classic, since it involves having a one-size-fit-them-all solution.
If "all" is finite and known in advance, then I'd advice you use an enum (in which case the compiler will choose to use the biggest size):

/// (Not strictly needed with this pattern)
trait IsOperation { fn a(&self); }

struct OpNeg { /* ... */ }
impl IsOperation for OpNeg { /* ... */ }

struct OpAdd { /* ... */ }
impl IsOperation for OpAdd { /* ... */ }
// etc.

/// No generic; but an explicit enumeration
enum Operation {
    OpNeg(OpNeg),
    OpAdd(OpAdd),
    // etc.
}
// This is a trick to alleviate further "copy-paste" matches
macro_rules! use_Operation {(
    let Op($pat:pat) = $expr;
    $($body:tt)*
) => ({
    use $crate::Operation::*;
    match $expr {
        OpNeg($pat) => { $($body)* },
        OpAdd($pat) => { $($body)* },
        // etc.
    }
})}

impl IsOperation for Operation {
    fn a(&self) {
        use_Operation!(
            let Op(ref op) = *self;
            op.a()
        )
    }
}

struct Struct {
    name: String,
    operation: Operation,
}

Else, you cannot have one-size-fit-them-all without indirection; if you can afford it, then you can do it "the Python way" and use trait objects (either with references & 'lifetimes around or Boxes if you don't mind heap-allocating):

trait Operation { fn a(&self); }

struct Struct {
    name: String,
    operation: Box<dyn Operation>,
}
2 Likes

Since defining the matching macro is too WET for my taste, I have written a meta-macro to automagically derive such macro, resulting in the following code being correct:

derive_match!{matching_BinOp in [crate],
    #[derive(Debug)]
    pub
    enum BinOp {
        Add { left: i32, right: i32 },
        Sub { left: i32, right: i32 },
    }
}

impl BinOp {
    fn left_eq_right (&self) -> bool
    {
        matching_BinOp! {
            let Op { ref left, ref right } = *self;
            left.eq(right)
        }
    }
}

#[derive(Debug)]
struct Struct {
    name: String,
    operation: BinOp,
}

fn main ()
{
    let mut foo = Struct {
        name: "Metamatician".into(),
        operation: BinOp::OpAdd { left: 21, right: 21 },
    };
    dbg!(&foo);
    assert!(foo.operation.left_eq_right());

    foo.operation = BinOp::OpSub { left: 69, right: 27 };
    dbg!(&foo);
    assert!(::core::ops::Not::not(
        foo.operation.left_eq_right()
    ));
}

where

impl BinOp {
    fn left_eq_right (&self) -> bool
    {
        matching_BinOp! {
            let Op { ref left, ref right } = *self;
            left.eq(right)
        }
    }
}

expands to

impl BinOp {
    fn left_eq_right (&self) -> bool
    {
        use crate::BinOp::*;
        match *self {
            OpAdd { ref left, ref right } => {
                left.eq(right)
            },
            OpSub { ref left, ref right } => {
                left.eq(right)
            },
        }
    }
}

I am thinking about publishing the meta-macro in its own crate, although it will most probably get added to ::candy. What do you think?

1 Like

Is the meta-macro generically useful as is, or is it primarily just instructive? If the former, a separate crate makes some sense, but if it's just the latter then perhaps you could just expand the documentation of ::candy to point out and summarize this item of interest.

1 Like

I don't really know yet. It's definitely worth blog-posting about (and I shall), and I am already using it in one of my projects that has many such enums; but since it is not as clean-looking as a #[derive] (it cannot be, due to proc-macro hygiene not being able to expand to macros) I am hesitant as to whether people will want to use it or define a macro each time they have such a need.