valkyrie
valkyrie

Reputation: 106

rust - methods as argument including a self reference

I have the following problem: I have two structs A and B, A implements a method, which takes another method as argument. I then create an instance of A within B. I then call the method of A with a method of B as argument. However, the passed method of B contains self as argument, since I have to access some other fields of B within the passed method.

My current plan would be to pass the method itself and self referencing an instance of B to the method in A. However, I struggle with the type definitions. I can hardcode the type of the second parameter to be B, however, I would like to keep it open, in case a struct C would also like to use the method in A. I therefore would like to set the type of the second parameter to the type of the struct, where the method in the first parameter is originating. Is there a way for achieving this? I was thinking about generics, however, I was not yet able to implement it.

Edit: added a minimal example in rust playground. What bothers me about this code, is, that I want to get rid of the hard coded &B in some_func_a, since this does not work with struct C.

struct A {}

impl A {
    fn some_func_a(&self, passed_fn: fn(&B, i32), cur_self: &B) {
        passed_fn(cur_self, 21);
    }
}

struct B {
    some_val: i32,
}

struct C {
    some_val: u32, // different field here than in B
}

impl B {
    fn call_fn_in_a(&self, a: A) {
        a.some_func_a(B::some_func_b, self);
    }

    fn some_func_b(&self, passed_val: i32) {
        println!("The value is {}, passed was {}", self.some_val, passed_val)
    }
}

impl C {
    fn call_fn_in_a(&self, a: A) {
        // this line here breaks, since &B is hard coded
        a.some_func_a(C::some_func_c, self);
    }

    fn some_func_c(&self, passed_val: i32) {
        println!("this is a new function, val is {}, passed was {}", self.some_val, passed_val)
    }
}

fn main() {
    let a = A {};
    let b = B {
        some_val: 42,
    };
    
    let b = B {
        some_val: 42,
    };
    b.call_fn_in_a(a);
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=53ac44cb660d180b3c7351e394355692

Upvotes: 4

Views: 2697

Answers (1)

cdhowie
cdhowie

Reputation: 168998

It sounds like you want a closure instead of a function pointer. This would allow you to eliminate the extra parameter of A::some_func_a() altogether:

impl A {
    fn some_func_a(&self, passed_fn: impl FnOnce(i32) -> ()) {
        passed_fn(21);
    }
}

Now we can call this function with any closure matching the call signature. For example:

impl B {
    fn call_fn_in_a(&self, a: A) {
        a.some_func_a(|v| self.some_func_b(v));
    }

    fn some_func_b(&self, passed_val: i32) {
        println!("The value is {}, passed was {}", self.some_val, passed_val)
    }
}

And likewise for C.

Now there are no restrictions on what extra data the function needs. Before there wasn't even a way to express the situation where the callback didn't need the reference parameter. Now it doesn't matter because the closure can capture what it needs, and it's no longer A::some_func_a()'s responsibility.

(Playground)


Side note: you probably want your call_fn methods to take the A by reference (&A), not by value. Otherwise calling these functions consumes the A value, requiring you to make another one to call the functions again.

Upvotes: 2

Related Questions