I am trying to make a ski calculus simulation system. I want that user can extend their own combinator. Here is my implementation. I delete many unrelated code to emphasize my problem.
// src/ski.rs
pub enum SKI {
SKISymbol,
SKICombinator(Box<SKICombinator>)
}
// src/skicombinator.rs
pub trait SKICombinator {}
pub struct S { }
impl SKICombinator for S {}
// src/print.rs, I defined new property here, I use print as example here
pub trait Print {
fn print (&self)
}
impl Print for SKI {
fn print(&self) {
match *self {
SKI::SKISymbol => println!("skisymbol"),
SKI::SKICombinator(ref c) => c.print(),
}
}
}
impl Print for SKICombinator {
fn print(&self) { unreachable!() } // have to do this if I want to call .print()
}
impl Print for S {
fn print(&self) { println!("S") }
}
// main.rs
pub fn main() {
let a = SKI::SKICombinator(Boc::new(S{}));
a.print();
}
I execute it and I hit into unreachable.
I supposed dynamic dispatch should work and it will print "S". Can anyone help me?
You're not using dynamic dispatch. You're invoking Print::print
on an SKICombinator
... so it calls the Print::print
that you defined for SKICombinator
.
I assume you wanted to call Print::print
for S
. You can't do that, because you have a Box<SKICombinator>
, which only allows you to call methods defined in the SKICombinator
trait. There is no print
method defined. In fact, SKICombinator
has no methods, so there's nothing you can call on it.
If you want to do dynamic dispatch through SKICombinator
, you need to define the methods in the trait itself. Also note that Rust does not have inheritance, so writing
pub trait SKICombinator: Print {}
won't work. Rust also doesn't have dynamic casting, so you can't "cross cast" from SKICombinator
to Print
.
One thing you can do is add something to SKICombinator
directly to do casting:
pub trait SKICombinator {
fn as_print(&self) -> Option<&Print> { None /* by default */ }
}
If an implementation of SKICombinator
implements Print
, it can then also implement as_print
to give callers access to it.
2 Likes
Thank for the reply.
Just some following question:
In my example, the s.print() can be called. and it will go to unreachable!.
When I write
impl Print for SKICombinator
I actually add a method into the trait, that is what I thought. So when I call print()
it should goes into the Box<SKICombinator>
and found it is a S{}
instance. Or maybe something is wrong?
And just to make sure that, what I do is impossible ... right? If I defined a trait and later want to extend it for other method without modify the trait implementation. Each instance can make their own version of print and use dynamic dispatch on the Enum
that has Box<Trait>
.
Maybe I should say more detail so you can help me better. I make the SKI enum and SKICombinator trait. User can define their combinator if they liked.
Now I need to implement a method called transform
to translate SKI calculus to another type of calculus. The transform
(print in my example) method need to go through the tree and called transform on each node.
At the node of SKI::SKICombinator
. Since different combinator will return different result. It also has to invoke the method defined in each instance, which I failed to implement here.
No, you implemented Print
on the trait. For dynamic dispatch, you want to implement methods on the underlying type, which are then called via the trait. This is implementing a method on the trait, which is then called directly via static dispatch.
Implementing a trait directly for another trait is almost never what you want.
Again, if you want to be able to call a method through a Box<Trait>
(or &Trait
, Rc<Trait>
, etc.), you can only do so via methods defined in that trait. You'll need to put print
(or an as_print
, or whatever you need) in the SKICombinator
trait directly.
OK, I understood, thanks.