Default for struct with generic types

#1

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?

#2

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

#3

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

#4

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=073e9ecbbeb559cda6385515ecfd958e

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

1 Like
#5

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
#6

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
#7

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
#8

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
#9

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
#10

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.

#11

So what you nedd is not generic, you just make it depending on the trait and any class that implement the trait will be usable.