Eric Wu
Eric Wu

Reputation: 15

How to implement arbitrary add operator in Rust?

I'm working on an arbitrary expression evaluator in Rust,

Take Add operator as an example:

fn eval_add<T: ?Sized + Add<T, Output=T>>(l: Rc<Any>, r: Rc<Any>) -> Rc<Any> {
    l.downcast_ref::<Add<T, Output=T>>().unwrap() +
    r.downcast_ref::<Add<T, Output=T>>().unwrap()
}

and I got such error from the compiler:

error: the downcast_ref method cannot be invoked on a trait object

It's obvious that the compiler doesn't know how to cast Any to std::ops::Add.

So what is the best practice to do such thing?

Upvotes: 0

Views: 376

Answers (1)

Peter Hall
Peter Hall

Reputation: 58735

It's obvious that the compiler doesn't know how to cast Any to std::ops::Add.

That's because Add is a trait and you may only downcast to a type.

This doesn't work:

l.downcast_ref::<Add<T, Output=T>>()

Because Add is a trait, so this is really:

l.downcast_ref::<dyn Add<T, Output=T>>()

What you probably intended was just:

l.downcast_ref::<T>()

Since T is the type variable in scope that implements Add.

Your requirements are quite unclear, and the setup seems a little strange: you are passing around Rc<dyn Any>, but you also have this T parameter, which can only mean that that caller knows the concrete type of these dyn Any arguments, in order to supply the correct T. It's hard to say that this is the "correct" answer, because there are choices here that may not meet the unstated requirements, but it "works" and resembles the code in your question:

use std::rc::Rc;
use std::any::Any;
use std::ops::Add;

fn eval_add<T>(l: Rc<dyn Any>, r: Rc<dyn Any>) -> Rc<dyn Any> 
where
    T: Add<T, Output = T> + Copy + 'static
{
    let l = *l.downcast_ref::<T>().unwrap();
    let r = *r.downcast_ref::<T>().unwrap();
    Rc::new(l + r)
}

Note that Add::add takes its argument by value, so you must copy or clone it, since it is borrowed from the Rc. I've added the Copy bound, which applies to most numeric types, which should be sufficient. If not, you could Clone instead, which is more general but potentially less efficient.

If the two arguments may have different types, then you'll have to introduce another type parameter, S, and constrain T: Add<S, Output = T>. At this point I would again question what you are doing and suggest that you might need to rethink your overall design, since this is all very un-Rusty and confusing.

Rather than using dyn Any, I would strongly suggest that you consider an enum of supported types. The code will likely be easier to understand and debug, and should be faster too.

Upvotes: 3

Related Questions