Subtypes using generics-like construction
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
add a comment |
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
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
add a comment |
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
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
generics types rust
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
add a comment |
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
add a comment |
1 Answer
1
active
oldest
votes
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.
add a comment |
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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.
add a comment |
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.
add a comment |
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.
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.
edited Nov 10 at 20:25
answered Nov 10 at 20:07
Shepmaster
147k11281415
147k11281415
add a comment |
add a comment |
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
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