Storing arbitrary function callables into a hashmap in Rust - rust

I am coming from Python and learning Rust. I am trying to store arbitrary functions (or pointers to functions) in a hashmap. And retrieve the function by string key and execute it. Basically trying to convert this simple Python code into Rust.
def foo(x, y):
return x + y
def bar(x, y, z):
return x + y + z
if __name__ == '__main__':
dict = {}
dict['foo'] = foo
dict['bar'] = bar
print(dict["foo"](1,2))
Because of the difference in the number of arguments, Rust compiler doesn't accept this kind of code (I believe Rust needs to know exact size of the function in hashmap values, and it needs to be consistent across storing functions). So I am wondering if anyone has a correct way (or Rust way) to do this. I also tried with matching, but is failing with the same reason.
The closest I got in Rust was something like
fn foo(x: i32, y: i32) -> i32 {
x + y
}
fn bar(x: i32, y: i32, z: i32) -> i32 {
x + y + z
}
fn main() {
let mut dict: std::collections::HashMap<&str, fn(i32, i32) -> i32> = std::collections::HashMap::new();
dict.insert("foo", foo);
dict.insert("bar", bar);
println!("{}", (dict["foo"])(1, 2));
}
But this line dict.insert("bar", bar); isn't compiling because the function bar has 3 arguments instead of 2.

You can store Box<dyn Any>:
fn main() {
let mut dict: std::collections::HashMap<&str, Box<dyn std::any::Any>> =
std::collections::HashMap::new();
dict.insert("foo", Box::new(foo as fn(i32, i32) -> i32));
dict.insert("bar", Box::new(bar as fn(i32, i32, i32) -> i32));
let v = dict["foo"].downcast_ref::<fn(i32, i32) -> i32>().unwrap()(1, 2);
println!("{v}");
}
However, I'd recommend you to rethink your design. Needing so much dynamism is rare in Rust, and perhaps your Python knowledge is leading you in the wrong direction.

Related

Mismatched types parameter error on rust generics

expected type parameter T, found type parameter A error display. I have written lifetime implementation code also but it stills doesn't solve the problem. What's wrong I am doing?
fn main() {
let x = 3;
let y = 5.0;
let max_value = max(x, y);
println!("The maximum value is {}", max_value);
}
fn max<T: PartialOrd, A: PartialOrd>(x: T, y: A) -> T {
if x > y {
x
} else {
y
}
}
// fn main() {
// let x = 3;
// let y = 5.0;
// let max_value = max(&x, &y);
// println!("The maximum value is {}", max_value);
// }
// fn max<'a, T: PartialOrd + Copy, A: PartialOrd + Copy>(x: &'a T, y: &'a A) -> &'a T {
// if x > y {
// x
// } else {
// y
// }
// }
T and A do not have to be the same type, so you have two problems.
The first is that you constrain T and A to be PartialOrd, which is the same thing as PartialOrd<Self>. So your actual constraints are T: PartialOrd<T>, A: PartialOrd<A>. This means you can compare the order of T's to other T's and A's to other A's, but x > y compares a T to an A.
Instead, you need to constrain T: PartialOrd<A>. (This also fails, but because of the invocation in main() -- more on that later.)
Second, the function is declared to return T but the else block returns y, which is not a T. Rust is statically typed, so it expects the types to exactly match.
This could be fixed by requiring that A can be converted to T (that is, A: Into<T>) and then you can return y.into() from the else block.
So at this point, we have:
fn main() {
let x = 3;
let y = 5.0;
let max_value = max(x, y);
println!("The maximum value is {}", max_value);
}
fn max<T: PartialOrd<A>, A: Into<T>>(x: T, y: A) -> T {
if x > y {
x
} else {
y.into()
}
}
But now you are left with more problems:
There are no types T and A satisfying T: PartialOrd<A> where T is an integer and A is a float, therefore you cannot call this function with 3 and 5.0 as you do in main().
Likewise, there's no implementation of Into<T> on A for an integer type T and a float type A.
x > y will move x and y, and then you cannot return them later. This is trivially fixed by constraining both T and A to be Copy.
The second issue could be fixed by having an enum that means "either T or A" and returning that instead. The either crate has such a type called Either, which we can use here as Either<T, A>:
use either::Either;
fn main() {
let x = 3;
let y = 5.0;
let max_value = max(x, y);
println!("The maximum value is {}", max_value);
}
fn max<T: PartialOrd<A> + Copy, A: Copy>(x: T, y: A) -> Either<T, A> {
if x > y {
Either::Left(x)
} else {
Either::Right(y)
}
}
(The println! works because Either<T, A> implements Display when both T and A do.)
You are still left with the problem where there's no built-in ordering implementation between integers and floats.
A "hail mary" solution could be to require that T and A can both be converted to f64 and then convert x and y to f64 before comparing them:
use either::Either;
fn main() {
let x = 3;
let y = 5.0;
let max_value = max(x, y);
println!("The maximum value is {}", max_value);
}
fn max<T: Copy + Into<f64>, A: Copy + Into<f64>>(x: T, y: A) -> Either<T, A> {
if x.into() > y.into() {
Either::Left(x)
} else {
Either::Right(y)
}
}
This is the first bit of code we have that actually compiles, and this might be good enough for your purposes. There are still some issues that remain, however:
i64 and u64 cannot be losslessy converted to f64, therefore they do not implement Into<f64>, and so if you change let x = 3; to let x = 3u64; (or 3i64) compilation will again fail.
f64 does not implement Ord because it's possible for there to be two f64 values x and y that are not equal but neither is greater than the other -- if either value is NaN, for example. This won't cause your program to crash, but it may produce an unexpected or incorrect result.
I suspect that this is a learning exercise, so hopefully this answer helps you understand what is wrong with the original code. I would not recommend a function like this in a real-world program; instead, it would be far better to convert both arguments to be of the same Ord-implementing type ahead of time and then you can use the built-in std::cmp::max function (or Ord::max).

Rust Polars .par_iter() for ChunkedArray<Float64Type>?

In Rust, using Polars, I am writing a custom function to be used within apply/map. This works well:
fn capita(x: Series) -> Result<Series> {
let y = x
.utf8()
.unwrap()
.par_iter() //ParallelIterator
However, if my Series was of type f64, then this doesn't work:
fn other_fn(x: Series) -> Result<Series> {
let y = x
.f64()
.unwrap()
.par_iter() // <--- no method par_iter found for reference ChunkedArray<Float64Type>
Is there a possible workaround? Looking at utf8.rs I need something like that but for Float64Chunked type.
Many thanks
EDIT
I think this workaround has worked, although turned out to be slower, AND giving an unexpected result. using par_bridge:
pub fn pa_fa(s: &mut [Series])->Result<Series>{
let u = s[2].f64()?;
let n = s[1].f64()?;
let n_iter = n.into_iter();
let c: Vec<f64> = n_iter.zip(u).par_bridge().map(
// ignore this line let c: Vec<f64> = n_iter.zip(u).map(
|(n,u)|{
n.unwrap().powf(1.777)*u.unwrap().sqrt()
}
).collect();
Ok(Series::new("Ant", c))

&[T] changed to Vec<&T>

I wrote a function that accepts a slice of single digit numbers and returns a number.
pub fn from_digits<T>(digits: &[T]) -> T
where
T: Num + FromPrimitive + Add + Mul + Copy,
{
let mut ret: T = T::zero();
let ten: T = T::from_i8(10).unwrap();
for d in digits {
ret = ret * ten + **d;
}
ret
}
For example, from_digits(&vec![1,2,3,4,5]) returns 12345. This seems to work fine.
Now, I want to use this function in another code:
let ret: Vec<Vec<i64>> = digits // &[i64]
.iter() // Iter<i64>
.rev() // impl Iterator<Item = i64>
.permutations(len) // Permutations<Rev<Iter<i64>>>
.map(|ds| from_digits(&ds)) // <-- ds: Vec<&i64>
.collect();
The problem is that after permutations(), the type in the lambda in map is Vec<&i64>, not Vec<i64>. This caused the compile error, as the expected parameter type is &[T], not &[&T].
I don't understand why the type of ds became Vec<&i64>. I tried to change the from_digits like this:
pub fn from_digits<T>(digits: &[&T]) -> T
...
But I am not sure if this is the correct way to fix the issue. Also, this caused another problem that I cannot pass simple data like vec![1,2,3] to the function.
Can you let me know the correct way to fix this?
The problem is that slice's iter() function returns an iterator over &T, so &i64.
The fix is to use the Iterator::copied() or Iterator::cloned() adapters that converts and iterator over &T to an iterator over T when T: Copy or T: Clone, respectively:
digits.iter().copied().rev() // ...

Reference to a primitive operator function

In Rust, is there any manner to handle operator functions such as add, or sub? I need to get the reference for those functions, but I can only find about traits. I'll leave here a comparative of what I need (like the wrapper methods) in Python.
A = 1
B = 2
A.__add__(B)
#Or maybe do something more, like
C = int(1).__add__
C(2)
You can obtain a function pointer to a trait method of a specific type via the universal function call syntax:
let fptr = <i32 as std::ops::Add>::add; // type: `fn(i32, i32) -> i32`
fptr(1, 3); // returns 4
Bigger example (Playground):
use std::ops;
fn calc(a: i32, b: i32, op: fn(i32, i32) -> i32) -> i32 {
op(a, b)
}
fn main() {
println!("{}", calc(2, 5, <i32 as ops::Add>::add)); // prints 7
println!("{}", calc(2, 5, <i32 as ops::Sub>::sub)); // prints -3
println!("{}", calc(2, 5, <i32 as ops::Mul>::mul)); // prints 10
}
Your int(1).__add__ example is a bit more complicated because we have a partially applied function here. Rust does not have this built into the language, but you can easily use closures to achieve the same effect:
let op = |b| 1 + b;
op(4); // returns 5

Adding two numbers without cloning both

use std::ops::Add;
#[derive(Debug)]
pub struct Vec3<N>{
x: N,
y: N,
z: N
}
impl<N> Vec3<N>{
pub fn new(x: N, y: N , z: N) -> Vec3<N>{
Vec3{x:x,y:y,z:z}
}
}
impl<N : Clone + Add<Output=N>> Vec3<N>{
pub fn add(&mut self,v: &Vec3<N>){
self.x = self.x.clone() + v.x.clone();
self.y = self.y.clone() + v.y.clone();
self.z = self.z.clone() + v.z.clone();
}
}
impl<N: Add<Output=N>> Add for Vec3<N>{
type Output = Vec3<N>;
fn add(self, v: Vec3<N>) -> Vec3<N>{
Vec3{x: self.x + v.x
,y: self.y + v.y
,z: self.z + v.z}
}
}
This allows me to write.
mod vec3;
use vec3::*;
fn main() {
let mut v1 = Vec3::<f32>::new(1.0,2.0,3.0);
let v2 = Vec3::<f32>::new(1.0,2.0,3.0);
v1.add(&v2);
let v3 = v1 + v2;
println!("{:?}", v3);
}
This let v3 = v1 + v2; consumes v1 and v2. But that is probably not always wanted, so I added another add function with the signature pub fn add(&mut self,v: &Vec3<N>)
My problem is with this code snippet
impl<N : Clone + Add<Output=N>> Vec3<N>{
pub fn add(&mut self,v: &Vec3<N>){
self.x = self.x.clone() + v.x.clone();
self.y = self.y.clone() + v.y.clone();
self.z = self.z.clone() + v.z.clone();
}
}
I needed to clone the values of both vectors in order to avoid a move. But I really wanted to write it like this
self.x = self.x + v.x.clone(); or self.x += v.x.clone(); I don't see why I would have to clone both values.
How could this be done?
There appears to be no available way to overload "+=" operator. However, you can avoid using exclipt "clone" if you replace Clone trait with Copy (however, you can use them together if needed):
impl<N: Copy + Add<Output = N>> Vec3<N> {
pub fn add(&mut self, v: &Vec3<N>){
self.x = self.x + v.x;
self.y = self.y + v.y;
self.z = self.z + v.z;
}
}
Notice how you don't have to call "clone" at all!
This is a direct quote from Rust's documentation:
When should my type be Copy?
Generally speaking, if your type can implement Copy, it should. There's one important thing to consider though: if you think your type may not be able to implement Copy in the future, then it might be prudent to not implement Copy. This is because removing Copy is a breaking change: that second example would fail to compile if we made Foo non-Copy.
You can find more information about Copy trait here.
Seems not to be possible at the moment. The reason is a missing += operator overload.
But clone() on primitive types seems to be just a noop so it actually doesn't matter, I guess.

Resources