0

I have a function called new_vec. It takes two vectors and creates a new one, by performing an elementwise operation on the pair of elements from the zipped vectors.

fn main() {
    let v1s = vec![1, 0, 1];
    let v2s = vec![0, 1, 1];
    let v3s = new_vec(v1s, v2s);
    println!("{:?}", v3s) // [1, 1, 2]
}

fn new_vec(v1s: Vec<i32>, v2s: Vec<i32>) -> Vec<i32> {
    let mut v3s = Vec::<i32>::new();
    for (v1, v2) in v1s.iter().zip(v2s.iter()) {
         v3s.push(v1 + v2) // would also like to use -
    }
    v3s
}

I want to have a new_vec function for the common binary operation that is possible to use on two integers, such as +, -, /, *.

How do I do this? I can imagine two ways: macros and closures. A minimal example of how to do this in the best way, for example with + and - would be appreciated.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
The Unfun Cat
  • 29,987
  • 31
  • 114
  • 156

1 Answers1

5

I would pass a closure:

fn new_vec<F>(v1s: &[i32], v2s: &[i32], foo: F) -> Vec<i32>
    where F: Fn(i32, i32) -> i32
{
    let mut v3s = Vec::<i32>::new();
    for (&v1, &v2) in v1s.iter().zip(v2s.iter()) {
        v3s.push(foo(v1, v2))
    }
    v3s
}

fn main() {
    let v1s = vec![1, 0, 1];
    let v2s = vec![0, 1, 1];
    let v3s = new_vec(&v1s, &v2s, |x, y| x - y);
    let v4s = new_vec(&v1s, &v2s, |x, y| x + y);
    println!("{:?}", v3s); // [1, -1, 0]
    println!("{:?}", v4s); // [1, 1, 2]
}

Note the change in the first two parameters; if your function doesn't need to consume its arguments, references are preferable to Vectors - in this case &[i32].

This implementation is not too efficient because the resulting Vector is extended incrementally; it's better if you modified it as follows to reduce the number of allocations:

fn new_vec<F>(v1s: &[i32], v2s: &[i32], foo: F) -> Vec<i32>
    where F: Fn(i32, i32) -> i32
{
    v1s.iter().zip(v2s.iter()).map(|(&x, &y)| foo(x, y)).collect()
}
Community
  • 1
  • 1
ljedrz
  • 20,316
  • 4
  • 69
  • 97
  • Love the haskellish where! It is so counterintuitive to me that closures aren't slow. Do you know if I can get the compiler to treat the closures as if I had created different functions for each operation (add_vec, minus_vec)? (Will need to look closer at the answer tomorrow before accepting.) – The Unfun Cat Oct 25 '16 at 17:51
  • @TheUnfunCat I'm not sure this is what you are after, but you can define a closure in a let binding, e.g. `let add = |x, y| x + y;` and then pass it like a regular function argument: `new_vec(&v1s, &v2s, add)`. – ljedrz Oct 25 '16 at 18:00
  • What I hoped to achieve: the same speed as if I had written the functions out in full and then compiled the file :) – The Unfun Cat Oct 25 '16 at 18:05
  • @TheUnfunCat you won't see any difference in this specific case; I compared the optimized LLVM-IR and ASM results for a classic function `fn foo(x: i32, y: i32) -> i32 { x + y }` against a closure `|x: i32, y: i32| x + y` (with some side effect so that it was not optimized away completely) and they were exactly the same. – ljedrz Oct 25 '16 at 18:19
  • 1
    @TheUnfunCat I think the term you are looking for is "Monomorphization". – Simon Whitehead Oct 25 '16 at 20:18