Subtypes using generics-like construction









up vote
0
down vote

favorite












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















up vote
0
down vote

favorite












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













up vote
0
down vote

favorite









up vote
0
down vote

favorite











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

146k11279413




146k11279413










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

















up vote
2
down vote



accepted










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',
    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








    up vote
    2
    down vote



    accepted










    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


























      up vote
      2
      down vote



      accepted










      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
























        up vote
        2
        down vote



        accepted







        up vote
        2
        down vote



        accepted






        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

        146k11279413




        146k11279413



























            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







            Popular posts from this blog

            Kleinkühnau

            Makov (Slowakei)

            Deutsches Schauspielhaus