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.
generics types rust
add a comment |
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.
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 |
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.
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
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
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
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.
add a comment |
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.
add a comment |
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.
add a comment |
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.
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
143k11268399
143k11268399
add a comment |
add a comment |
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