How to call member functions from the reference of structure?

Hi,
I am trying to write a similar code to the followings in rust.
But, as far as I've studied, it seems there's no straightforward way to do it while it's simple in c++.
Could someone give me a hint?
Thank you so much!

#include <iostream>
struct Parent
{
   virtual void func() = 0;
};

struct A: Parent {
   void func() {
     std::cout << "I am A" << std::endl;
   }
};

struct B: Parent {
   void func() {
     std::cout << "I am B" << std::endl;
   }
};

int main(){
   struct Sample {
      Parent *p;
   };

   Sample smp = {new A()}; // can be switched to B
   smp.p->func();
}

The C++ example involves OOP, and in particular inheritance which has no direct equivalent in Rust; but the closest thing to a superclass with virtual methods in Rust is trait objects.

You code also involves pointers. Judging by the fact that you use a new constructor to create the pointer, I’m assuming that Sample is supposed to own the contained Parent, so in Rust I use Box.

The full “translated” code looks as follows

trait Parent {
    fn func(&self);
}

struct A;

impl Parent for A {
    fn func(&self) {
        println!("I am A");
    }
}

struct B;

impl Parent for B {
    fn func(&self) {
        println!("I am B");
    }
}

fn main() {
    struct Sample {
        p: Box<dyn Parent>,
    }
    
    let smp = Sample {
        p: Box::new(A), // can be switched to B
    };
    
    smp.p.func();
}

Note that for a statically known list of “types” that are supposed to be allowed as a Parent, you can also use an enum instead of trait objects. E.g.

enum Parent {
    A,
    B,
}

impl Parent {
    fn func(&self) {
        match self {
            Parent::A => println!("I am A"),
            Parent::B => println!("I am B"),
        }
    }
}

fn main() {
    struct Sample {
        p: Parent,
    }
    
    let smp = Sample {
        p: Parent::A, // can be switched to Parent::B
    };
    
    smp.p.func();
}
2 Likes

Thank you so much for very quick response! These are great examples for me.
One thing I am wondering is if the struct A and struct B has some variables and we want to use them, what's the best way for it.

In c++, it'll be like this.

#include <iostream>
struct Parent
{
   virtual void func() = 0;
   int num;
};

struct A: Parent {
   void func() {
     std::cout << "I am A: " << num << std::endl;
   }
};

struct B: Parent {
   void func() {
     std::cout << "I am B: " << num << std::endl;
   }
};

int main(){
   struct Sample {
      Parent *p;
   };

   Sample smp = {new A()};
   smp.p->num = 10;
   smp.p->func();
}

I tried to covert it based on your example and got the following.
But, since trait cannot have member variables, the code looks a bit complex.
Is this a right way?

trait Parent {
    fn setnum(&mut self, val: u32);
    fn num(&self) -> u32;
    fn func(&self);
}

struct A {
  num: u32,
}

impl Parent for A {
    fn func(&self) {
        println!("I am A {}", self.num);
    }
    fn num(&self) -> u32 {
       self.num
    }
    fn setnum(&mut self, val: u32) {
       self.num = val;
    }
}

struct B {
  num: u32,
}

impl Parent for B {
    fn func(&self) {
        println!("I am B {}", self.num);
    }
    fn num(&self) -> u32 {
       self.num
    }
    fn setnum(&mut self, val: u32) {
       self.num = val;
    }
}
fn main() {
    struct Sample {
        p: Box<dyn Parent>,
    }

    let mut smp = Sample {
        p: Box::new(B{num: 0}), // can be switched to B
    };

    smp.p.setnum(10);
    smp.p.func();
}

It certainly is a way to do this. What “the best” way would be probably depends on the concrete use-case. Rust likes to decouple data from behavior. A superclass with fields and virtual methods is a case of rather tight coupling of data and behavior, and raises the question whether the practical use case, if any, where you use this kind of construct might perhaps be solved better a different way, anyway:

Maybe a getter and setter would’ve been the better approach in C++, too. Maybe, it would be nicer to pass a view to the u32 as an additional argument to func, and store it outside of the Parent, directly in the Sample struct. The “best” solution is going to be what’s the easiest to work with in the concrete use-case, and hard to answer in a vacuum with just a Foobar-esque example.

2 Likes

Thank you so much for the explanation. I got it.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.