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

143k11268399




143k11268399










asked Nov 10 at 19:26









vandenheuvel

286




286








  • 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

        143k11268399




        143k11268399






























             

            draft saved


            draft discarded



















































             


            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

            Florida Star v. B. J. F.

            Error while running script in elastic search , gateway timeout

            Adding quotations to stringified JSON object values