Is it possible to pass an object method as argument to a function and bind it to the object?

余生长醉 提交于 2020-08-24 07:15:34

问题


Is it possible to make a bind to object method? For example, I have a vector and a lot of functions which do something if some item exists in the vector. I would implement it as follows:

fn perform_if_exists(item: u8, vector: &Vec<u8>, func: fn(usize)) {
    let idx = vector.iter().position(|i| *i == item );
    match idx {
        Some(i) => func(i), 
        None    => {},
    }
}

fn main() {
    let v: Vec<u8> = vec![1, 2, 3];
    perform_if_exists(1, &v, Vec<_>::remove);
}

but it gives a lot of errors. I think they are reasonable but it's because I don't understand how to put vector's method as argument to a function.


回答1:


Is it possible

Sure it is. You have to fix the multiple cascading errors first:

  1. Invalid syntax: Vec<_>::remove isn't valid.
  2. Incompatible argument types: Vec::remove modifies a Vec, so you have to pass in a Vec somehow.
  3. Mutability: Vec::remove modifies a Vec, so you have to declare that the function is allowed to do so.
  4. Vec::remove returns the removed value, so you have to allow the function to return a value, even if it's thrown away.
fn perform_if_exists<F, R>(item: u8, vector: &mut Vec<u8>, func: F)
    where F: Fn(&mut Vec<u8>, usize) -> R
{
    let idx = vector.iter().position(|i| *i == item );
    if let Some(i) = idx {
        func(vector, i);
    }
}

fn main() {
    let mut v = vec![1, 2, 3];
    perform_if_exists(1, &mut v, Vec::remove);
    println!("{:?}", v);
}

I switched to a generic as that's generally how you will accept closures. A function pointer is fine but more restrictive.




回答2:


A method in Rust is nothing more than a function, which also takes a first self parameter. The method Vec::remove takes two arguments: &mut self and index: usize. The self parameter is always of type Self, which is Vec<u8> in this case. The complete type of Vec::<u8>::remove is: fn(&mut Vec<u8>, usize) -> u8 (yes it also returns the removed element).

After changing the type in your code (+ a few minor mistakes), it works:

//                                      vvv-- has to be mutable
fn perform_if_exists(item: u8, vector: &mut Vec<u8>, func: fn(&mut Vec<u8>, usize) -> u8) {
    let idx = vector.iter().position(|i| *i == item );
    match idx {
        Some(i) => { 
            func(vector, i);
        }, 
        None    => {},
    }
}

fn main() {
    let mut v: Vec<u8> = vec![1, 2, 3];
    perform_if_exists(1, &mut v, Vec::remove);
}

But fn(...) -> ... types are raw pointer types and just work for ordinary functions. Often you also want to enable the user to pass anything that is "callable", like closures. There are traits exactly for that purpose: Fn(...) -> ....

Let me propose another solution:

fn perform_if_exists<T, F, R>(item: T, vector: &mut Vec<T>, func: F) -> Option<R>
    where F: FnOnce(&mut Vec<T>, usize) -> R,
          T: PartialEq
{
    let idx = vector.iter().position(|i| *i == item );
    idx.map(|i| func(vector, i))
}

This solution is far more generic as it allows arbitrary item types, arbitrary "callable" types and returns the value that is returned by the given function. Note that the main function didn't change; the solution is more generic, but all old uses still work.



来源:https://stackoverflow.com/questions/37391695/is-it-possible-to-pass-an-object-method-as-argument-to-a-function-and-bind-it-to

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!