Subtypes using generics-like construction












0














I have a struct of which precisely two versions exist. That is, the struct has an attribute describing which version it is (a bool or enum with two variants) which is passed as an argument in the constructor. Which version the struct will have is known at compile time. In the majority of the methods of this struct, a corresponding method (on an attribute of this struct) is called, depending on the value of the attribute. This results in many if-statements throughout the impl of this struct.



I considered moving all of the code into a trait, but this didn't seem appropriate: dynamic dispatch is not necessary, almost all methods will not have the self parameter and setters / getters for all attributes will be required. I'd still be left with two identical struct declarations. Also, the trait doesn't describe any generic behavior that other structs should implement.



It would be ideal if I could instead create a generic version of this struct, and instantiate one of the two versions. To make, for lack of a better word, two "sub-types" of my struct, with only a single method that has different behavior. Is such a thing possible?



It concerns a performance critical application, and the methods on this struct will be called many times. It it wasn't for maintainability, I would just copy all code. I would create two almost identical structs, where inside the methods, there is either one version of an external method being called, or the other.










share|improve this question




















  • 1




    l if I could instead create a generic version of this struct, and instantiate one of the two versions — yes, do that. What is stopping you?
    – Shepmaster
    Nov 10 at 19:56










  • You answer clarified a lot. I opted for your first option; it fulfills most the requirements and preferences described above. I hadn't thought about creating two entirely new structs without any attributes (which is still slightly odd to me, it seems like that's not really what structs should be used for). Also, there is still the downside that the trait in this case doesn't describe any general behavior. There can only be two variants. This is however more or less mitigated by keeping the trait private to the current module. Thanks!
    – vandenheuvel
    Nov 11 at 9:31


















0














I have a struct of which precisely two versions exist. That is, the struct has an attribute describing which version it is (a bool or enum with two variants) which is passed as an argument in the constructor. Which version the struct will have is known at compile time. In the majority of the methods of this struct, a corresponding method (on an attribute of this struct) is called, depending on the value of the attribute. This results in many if-statements throughout the impl of this struct.



I considered moving all of the code into a trait, but this didn't seem appropriate: dynamic dispatch is not necessary, almost all methods will not have the self parameter and setters / getters for all attributes will be required. I'd still be left with two identical struct declarations. Also, the trait doesn't describe any generic behavior that other structs should implement.



It would be ideal if I could instead create a generic version of this struct, and instantiate one of the two versions. To make, for lack of a better word, two "sub-types" of my struct, with only a single method that has different behavior. Is such a thing possible?



It concerns a performance critical application, and the methods on this struct will be called many times. It it wasn't for maintainability, I would just copy all code. I would create two almost identical structs, where inside the methods, there is either one version of an external method being called, or the other.










share|improve this question




















  • 1




    l if I could instead create a generic version of this struct, and instantiate one of the two versions — yes, do that. What is stopping you?
    – Shepmaster
    Nov 10 at 19:56










  • You answer clarified a lot. I opted for your first option; it fulfills most the requirements and preferences described above. I hadn't thought about creating two entirely new structs without any attributes (which is still slightly odd to me, it seems like that's not really what structs should be used for). Also, there is still the downside that the trait in this case doesn't describe any general behavior. There can only be two variants. This is however more or less mitigated by keeping the trait private to the current module. Thanks!
    – vandenheuvel
    Nov 11 at 9:31
















0












0








0







I have a struct of which precisely two versions exist. That is, the struct has an attribute describing which version it is (a bool or enum with two variants) which is passed as an argument in the constructor. Which version the struct will have is known at compile time. In the majority of the methods of this struct, a corresponding method (on an attribute of this struct) is called, depending on the value of the attribute. This results in many if-statements throughout the impl of this struct.



I considered moving all of the code into a trait, but this didn't seem appropriate: dynamic dispatch is not necessary, almost all methods will not have the self parameter and setters / getters for all attributes will be required. I'd still be left with two identical struct declarations. Also, the trait doesn't describe any generic behavior that other structs should implement.



It would be ideal if I could instead create a generic version of this struct, and instantiate one of the two versions. To make, for lack of a better word, two "sub-types" of my struct, with only a single method that has different behavior. Is such a thing possible?



It concerns a performance critical application, and the methods on this struct will be called many times. It it wasn't for maintainability, I would just copy all code. I would create two almost identical structs, where inside the methods, there is either one version of an external method being called, or the other.










share|improve this question















I have a struct of which precisely two versions exist. That is, the struct has an attribute describing which version it is (a bool or enum with two variants) which is passed as an argument in the constructor. Which version the struct will have is known at compile time. In the majority of the methods of this struct, a corresponding method (on an attribute of this struct) is called, depending on the value of the attribute. This results in many if-statements throughout the impl of this struct.



I considered moving all of the code into a trait, but this didn't seem appropriate: dynamic dispatch is not necessary, almost all methods will not have the self parameter and setters / getters for all attributes will be required. I'd still be left with two identical struct declarations. Also, the trait doesn't describe any generic behavior that other structs should implement.



It would be ideal if I could instead create a generic version of this struct, and instantiate one of the two versions. To make, for lack of a better word, two "sub-types" of my struct, with only a single method that has different behavior. Is such a thing possible?



It concerns a performance critical application, and the methods on this struct will be called many times. It it wasn't for maintainability, I would just copy all code. I would create two almost identical structs, where inside the methods, there is either one version of an external method being called, or the other.







generics types rust






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 10 at 20:13









Shepmaster

147k11281415




147k11281415










asked Nov 10 at 19:26









vandenheuvel

667




667








  • 1




    l if I could instead create a generic version of this struct, and instantiate one of the two versions — yes, do that. What is stopping you?
    – Shepmaster
    Nov 10 at 19:56










  • You answer clarified a lot. I opted for your first option; it fulfills most the requirements and preferences described above. I hadn't thought about creating two entirely new structs without any attributes (which is still slightly odd to me, it seems like that's not really what structs should be used for). Also, there is still the downside that the trait in this case doesn't describe any general behavior. There can only be two variants. This is however more or less mitigated by keeping the trait private to the current module. Thanks!
    – vandenheuvel
    Nov 11 at 9:31
















  • 1




    l if I could instead create a generic version of this struct, and instantiate one of the two versions — yes, do that. What is stopping you?
    – Shepmaster
    Nov 10 at 19:56










  • You answer clarified a lot. I opted for your first option; it fulfills most the requirements and preferences described above. I hadn't thought about creating two entirely new structs without any attributes (which is still slightly odd to me, it seems like that's not really what structs should be used for). Also, there is still the downside that the trait in this case doesn't describe any general behavior. There can only be two variants. This is however more or less mitigated by keeping the trait private to the current module. Thanks!
    – vandenheuvel
    Nov 11 at 9:31










1




1




l if I could instead create a generic version of this struct, and instantiate one of the two versions — yes, do that. What is stopping you?
– Shepmaster
Nov 10 at 19:56




l if I could instead create a generic version of this struct, and instantiate one of the two versions — yes, do that. What is stopping you?
– Shepmaster
Nov 10 at 19:56












You answer clarified a lot. I opted for your first option; it fulfills most the requirements and preferences described above. I hadn't thought about creating two entirely new structs without any attributes (which is still slightly odd to me, it seems like that's not really what structs should be used for). Also, there is still the downside that the trait in this case doesn't describe any general behavior. There can only be two variants. This is however more or less mitigated by keeping the trait private to the current module. Thanks!
– vandenheuvel
Nov 11 at 9:31






You answer clarified a lot. I opted for your first option; it fulfills most the requirements and preferences described above. I hadn't thought about creating two entirely new structs without any attributes (which is still slightly odd to me, it seems like that's not really what structs should be used for). Also, there is still the downside that the trait in this case doesn't describe any general behavior. There can only be two variants. This is however more or less mitigated by keeping the trait private to the current module. Thanks!
– vandenheuvel
Nov 11 at 9:31














1 Answer
1






active

oldest

votes


















2














Use a trait for behavior that has multiple implementations. There's many combinations of ways you can use them, here's one:



use std::marker::PhantomData;

trait Core {
fn print();
}

#[derive(Debug, Default)]
struct PrintA;
impl Core for PrintA {
fn print() {
print!("a")
}
}

#[derive(Debug, Default)]
struct PrintB;
impl Core for PrintB {
fn print() {
print!("b")
}
}

#[derive(Debug, Default)]
struct Thing<C>(PhantomData<C>);

impl<C: Core> Thing<C> {
fn common() {
print!(">");
C::print();
println!("<")
}
}

fn main() {
Thing::<PrintA>::common();
Thing::<PrintB>::common();
}


Or another:



trait Core {
fn select<'a>(left: &'a i32, right: &'a i32) -> &'a i32;
}

#[derive(Debug, Default)]
struct Left;
impl Core for Left {
fn select<'a>(left: &'a i32, _right: &'a i32) -> &'a i32 {
left
}
}

#[derive(Debug, Default)]
struct Right;
impl Core for Right {
fn select<'a>(_left: &'a i32, right: &'a i32) -> &'a i32 {
right
}
}

#[derive(Debug, Default)]
struct Thing<C> {
kind: C,
left: i32,
right: i32,
}

impl Thing<Left> {
fn new_left(left: i32, right: i32) -> Self {
Self {
left,
right,
kind: Left,
}
}
}
impl Thing<Right> {
fn new_right(left: i32, right: i32) -> Self {
Self {
left,
right,
kind: Right,
}
}
}

impl<C: Core> Thing<C> {
fn add_one(&self) -> i32 {
self.select() + 1
}

fn select(&self) -> &i32 {
C::select(&self.left, &self.right)
}
}

pub fn l() -> i32 {
let l = Thing::new_left(100, 200);
l.add_one()
}

pub fn r() -> i32 {
let r = Thing::new_right(100, 200);
r.add_one()
}


Of note, this last example compiles down to the following LLVM IR:



define i32 @_playground_l() {
start:
ret i32 101
}

define i32 @_playground_r() {
start:
ret i32 201
}



I considered moving all of the code into a trait, but this didn't seem appropriate: dynamic dispatch is not necessary, almost all methods will not have the self parameter





  • traits don't imply dynamic dispatch. See monomorphization.

  • trait methods don't require self



It it wasn't for maintainability, I would just copy all code




Sounds like a place that macros might be a fit, if you cannot handle traits.






share|improve this answer























    Your Answer






    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "1"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53242628%2fsubtypes-using-generics-like-construction%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    1 Answer
    1






    active

    oldest

    votes








    1 Answer
    1






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    2














    Use a trait for behavior that has multiple implementations. There's many combinations of ways you can use them, here's one:



    use std::marker::PhantomData;

    trait Core {
    fn print();
    }

    #[derive(Debug, Default)]
    struct PrintA;
    impl Core for PrintA {
    fn print() {
    print!("a")
    }
    }

    #[derive(Debug, Default)]
    struct PrintB;
    impl Core for PrintB {
    fn print() {
    print!("b")
    }
    }

    #[derive(Debug, Default)]
    struct Thing<C>(PhantomData<C>);

    impl<C: Core> Thing<C> {
    fn common() {
    print!(">");
    C::print();
    println!("<")
    }
    }

    fn main() {
    Thing::<PrintA>::common();
    Thing::<PrintB>::common();
    }


    Or another:



    trait Core {
    fn select<'a>(left: &'a i32, right: &'a i32) -> &'a i32;
    }

    #[derive(Debug, Default)]
    struct Left;
    impl Core for Left {
    fn select<'a>(left: &'a i32, _right: &'a i32) -> &'a i32 {
    left
    }
    }

    #[derive(Debug, Default)]
    struct Right;
    impl Core for Right {
    fn select<'a>(_left: &'a i32, right: &'a i32) -> &'a i32 {
    right
    }
    }

    #[derive(Debug, Default)]
    struct Thing<C> {
    kind: C,
    left: i32,
    right: i32,
    }

    impl Thing<Left> {
    fn new_left(left: i32, right: i32) -> Self {
    Self {
    left,
    right,
    kind: Left,
    }
    }
    }
    impl Thing<Right> {
    fn new_right(left: i32, right: i32) -> Self {
    Self {
    left,
    right,
    kind: Right,
    }
    }
    }

    impl<C: Core> Thing<C> {
    fn add_one(&self) -> i32 {
    self.select() + 1
    }

    fn select(&self) -> &i32 {
    C::select(&self.left, &self.right)
    }
    }

    pub fn l() -> i32 {
    let l = Thing::new_left(100, 200);
    l.add_one()
    }

    pub fn r() -> i32 {
    let r = Thing::new_right(100, 200);
    r.add_one()
    }


    Of note, this last example compiles down to the following LLVM IR:



    define i32 @_playground_l() {
    start:
    ret i32 101
    }

    define i32 @_playground_r() {
    start:
    ret i32 201
    }



    I considered moving all of the code into a trait, but this didn't seem appropriate: dynamic dispatch is not necessary, almost all methods will not have the self parameter





    • traits don't imply dynamic dispatch. See monomorphization.

    • trait methods don't require self



    It it wasn't for maintainability, I would just copy all code




    Sounds like a place that macros might be a fit, if you cannot handle traits.






    share|improve this answer




























      2














      Use a trait for behavior that has multiple implementations. There's many combinations of ways you can use them, here's one:



      use std::marker::PhantomData;

      trait Core {
      fn print();
      }

      #[derive(Debug, Default)]
      struct PrintA;
      impl Core for PrintA {
      fn print() {
      print!("a")
      }
      }

      #[derive(Debug, Default)]
      struct PrintB;
      impl Core for PrintB {
      fn print() {
      print!("b")
      }
      }

      #[derive(Debug, Default)]
      struct Thing<C>(PhantomData<C>);

      impl<C: Core> Thing<C> {
      fn common() {
      print!(">");
      C::print();
      println!("<")
      }
      }

      fn main() {
      Thing::<PrintA>::common();
      Thing::<PrintB>::common();
      }


      Or another:



      trait Core {
      fn select<'a>(left: &'a i32, right: &'a i32) -> &'a i32;
      }

      #[derive(Debug, Default)]
      struct Left;
      impl Core for Left {
      fn select<'a>(left: &'a i32, _right: &'a i32) -> &'a i32 {
      left
      }
      }

      #[derive(Debug, Default)]
      struct Right;
      impl Core for Right {
      fn select<'a>(_left: &'a i32, right: &'a i32) -> &'a i32 {
      right
      }
      }

      #[derive(Debug, Default)]
      struct Thing<C> {
      kind: C,
      left: i32,
      right: i32,
      }

      impl Thing<Left> {
      fn new_left(left: i32, right: i32) -> Self {
      Self {
      left,
      right,
      kind: Left,
      }
      }
      }
      impl Thing<Right> {
      fn new_right(left: i32, right: i32) -> Self {
      Self {
      left,
      right,
      kind: Right,
      }
      }
      }

      impl<C: Core> Thing<C> {
      fn add_one(&self) -> i32 {
      self.select() + 1
      }

      fn select(&self) -> &i32 {
      C::select(&self.left, &self.right)
      }
      }

      pub fn l() -> i32 {
      let l = Thing::new_left(100, 200);
      l.add_one()
      }

      pub fn r() -> i32 {
      let r = Thing::new_right(100, 200);
      r.add_one()
      }


      Of note, this last example compiles down to the following LLVM IR:



      define i32 @_playground_l() {
      start:
      ret i32 101
      }

      define i32 @_playground_r() {
      start:
      ret i32 201
      }



      I considered moving all of the code into a trait, but this didn't seem appropriate: dynamic dispatch is not necessary, almost all methods will not have the self parameter





      • traits don't imply dynamic dispatch. See monomorphization.

      • trait methods don't require self



      It it wasn't for maintainability, I would just copy all code




      Sounds like a place that macros might be a fit, if you cannot handle traits.






      share|improve this answer


























        2












        2








        2






        Use a trait for behavior that has multiple implementations. There's many combinations of ways you can use them, here's one:



        use std::marker::PhantomData;

        trait Core {
        fn print();
        }

        #[derive(Debug, Default)]
        struct PrintA;
        impl Core for PrintA {
        fn print() {
        print!("a")
        }
        }

        #[derive(Debug, Default)]
        struct PrintB;
        impl Core for PrintB {
        fn print() {
        print!("b")
        }
        }

        #[derive(Debug, Default)]
        struct Thing<C>(PhantomData<C>);

        impl<C: Core> Thing<C> {
        fn common() {
        print!(">");
        C::print();
        println!("<")
        }
        }

        fn main() {
        Thing::<PrintA>::common();
        Thing::<PrintB>::common();
        }


        Or another:



        trait Core {
        fn select<'a>(left: &'a i32, right: &'a i32) -> &'a i32;
        }

        #[derive(Debug, Default)]
        struct Left;
        impl Core for Left {
        fn select<'a>(left: &'a i32, _right: &'a i32) -> &'a i32 {
        left
        }
        }

        #[derive(Debug, Default)]
        struct Right;
        impl Core for Right {
        fn select<'a>(_left: &'a i32, right: &'a i32) -> &'a i32 {
        right
        }
        }

        #[derive(Debug, Default)]
        struct Thing<C> {
        kind: C,
        left: i32,
        right: i32,
        }

        impl Thing<Left> {
        fn new_left(left: i32, right: i32) -> Self {
        Self {
        left,
        right,
        kind: Left,
        }
        }
        }
        impl Thing<Right> {
        fn new_right(left: i32, right: i32) -> Self {
        Self {
        left,
        right,
        kind: Right,
        }
        }
        }

        impl<C: Core> Thing<C> {
        fn add_one(&self) -> i32 {
        self.select() + 1
        }

        fn select(&self) -> &i32 {
        C::select(&self.left, &self.right)
        }
        }

        pub fn l() -> i32 {
        let l = Thing::new_left(100, 200);
        l.add_one()
        }

        pub fn r() -> i32 {
        let r = Thing::new_right(100, 200);
        r.add_one()
        }


        Of note, this last example compiles down to the following LLVM IR:



        define i32 @_playground_l() {
        start:
        ret i32 101
        }

        define i32 @_playground_r() {
        start:
        ret i32 201
        }



        I considered moving all of the code into a trait, but this didn't seem appropriate: dynamic dispatch is not necessary, almost all methods will not have the self parameter





        • traits don't imply dynamic dispatch. See monomorphization.

        • trait methods don't require self



        It it wasn't for maintainability, I would just copy all code




        Sounds like a place that macros might be a fit, if you cannot handle traits.






        share|improve this answer














        Use a trait for behavior that has multiple implementations. There's many combinations of ways you can use them, here's one:



        use std::marker::PhantomData;

        trait Core {
        fn print();
        }

        #[derive(Debug, Default)]
        struct PrintA;
        impl Core for PrintA {
        fn print() {
        print!("a")
        }
        }

        #[derive(Debug, Default)]
        struct PrintB;
        impl Core for PrintB {
        fn print() {
        print!("b")
        }
        }

        #[derive(Debug, Default)]
        struct Thing<C>(PhantomData<C>);

        impl<C: Core> Thing<C> {
        fn common() {
        print!(">");
        C::print();
        println!("<")
        }
        }

        fn main() {
        Thing::<PrintA>::common();
        Thing::<PrintB>::common();
        }


        Or another:



        trait Core {
        fn select<'a>(left: &'a i32, right: &'a i32) -> &'a i32;
        }

        #[derive(Debug, Default)]
        struct Left;
        impl Core for Left {
        fn select<'a>(left: &'a i32, _right: &'a i32) -> &'a i32 {
        left
        }
        }

        #[derive(Debug, Default)]
        struct Right;
        impl Core for Right {
        fn select<'a>(_left: &'a i32, right: &'a i32) -> &'a i32 {
        right
        }
        }

        #[derive(Debug, Default)]
        struct Thing<C> {
        kind: C,
        left: i32,
        right: i32,
        }

        impl Thing<Left> {
        fn new_left(left: i32, right: i32) -> Self {
        Self {
        left,
        right,
        kind: Left,
        }
        }
        }
        impl Thing<Right> {
        fn new_right(left: i32, right: i32) -> Self {
        Self {
        left,
        right,
        kind: Right,
        }
        }
        }

        impl<C: Core> Thing<C> {
        fn add_one(&self) -> i32 {
        self.select() + 1
        }

        fn select(&self) -> &i32 {
        C::select(&self.left, &self.right)
        }
        }

        pub fn l() -> i32 {
        let l = Thing::new_left(100, 200);
        l.add_one()
        }

        pub fn r() -> i32 {
        let r = Thing::new_right(100, 200);
        r.add_one()
        }


        Of note, this last example compiles down to the following LLVM IR:



        define i32 @_playground_l() {
        start:
        ret i32 101
        }

        define i32 @_playground_r() {
        start:
        ret i32 201
        }



        I considered moving all of the code into a trait, but this didn't seem appropriate: dynamic dispatch is not necessary, almost all methods will not have the self parameter





        • traits don't imply dynamic dispatch. See monomorphization.

        • trait methods don't require self



        It it wasn't for maintainability, I would just copy all code




        Sounds like a place that macros might be a fit, if you cannot handle traits.







        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Nov 10 at 20:25

























        answered Nov 10 at 20:07









        Shepmaster

        147k11281415




        147k11281415






























            draft saved

            draft discarded




















































            Thanks for contributing an answer to Stack Overflow!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.





            Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


            Please pay close attention to the following guidance:


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53242628%2fsubtypes-using-generics-like-construction%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            這個網誌中的熱門文章

            Tangent Lines Diagram Along Smooth Curve

            Yusuf al-Mu'taman ibn Hud

            Zucchini