Method Calling Via Function Pointer

I am a relative newbie so I'm hoping this question make sense. The following code is generating an error:

struct Callback{
	callback : Vec<fn(i32)->i32>,
}

impl Callback{
	fn new() -> Callback{
		let mut cb = Callback{
			callback : Vec::new(),
		};

		cb.callback.push(cb.add1);
		cb
	}

	fn call_via_jump_table (&self, index : usize, parameter : i32){
		println!("{}:{}", parameter, (self.callback[index])(parameter));
	}

	fn add1(&self, x : i32) -> i32 {
		x+1
	}
}

fn main() {	
	let callback = Callback::new();	
	callback.call_via_jump_table(0,5);
}

The error I'm getting is:

error[E0609]: no field `add1` on type `Callback`
  --> src/main.rs:12:23
   |
12 |         cb.callback.push(cb.add1);
   |                                            ^^^^ unknown field
   |
   = note: available field is: `callback`

If I move the method add1 out of the impl block (and remove the &self and "cb.") it compiles and runs fine.

It seems I'm really asking about creating jump tables inside a struct that uses methods from inside the struct but if you an help solve this specific issue, I think the rest will be clear.

Thanks in advance for your help and patience.

You can get a function pointer to the function add1 by the syntax Callback::add1. However, add1 is a function that takes two parameters, &Callback and i32, so it won't achieve the thing you are hoping for.

It is not possible for a function pointer to capture a method receiver, because a function pointer is exactly a pointer to machine code to execute, nothing else. There's no room for the &Callback to be carried along — and if there was, you wouldn’t like the results of trying to store an &Callback inside Callback (a “self-referential struct”, which is not supported by the language).

However, you can store function pointers that don't try to capture &Callback, and just provide the &Callback when you use them. That works for your example:

struct Callback {
    callback: Vec<fn(&Callback, i32) -> i32>,
}

impl Callback {
    fn new() -> Callback {
        Callback {
            callback: vec![Callback::add1],
        }
    }

    fn call_via_jump_table(&self, index: usize, parameter: i32) {
        println!("{}:{}", parameter, (self.callback[index])(self, parameter));
    }

    fn add1(&self, x: i32) -> i32 {
        x + 1
    }
}

But it won't work if you wanted to capture things other than &Callback. If you need any actual captured data, you can't use function pointers, and need Box<dyn Fn(i32) -> i32> or a related type instead, and to create it using closure syntax.

1 Like

Thank you! I very much appreciate your quick response. Maybe some day, I'll be smart enough to pay it forward (I clearly have a way to go :slight_smile: ).

I don't know what you meant by "capture things other than &Callback" and whether or not I will need to do that in my ultimate code. I suspect my use of this feature will be a simple jump table: index into the table and call the method whose pointer is stored there along with some parameters. That is, I think I'm using the pointer simply as a way to get to the method.

If you're just picking the method essentially “by name” through the lookup table, and the receiver (the value the method is “called on”) is the same in all cases, then you don't need to worry about capturing.

It seems you are expecting Rust to treat an un-called method as if it automatically were a partially applied closure capturing self. It isn't.

If you want that, you'll need to be explicit:

let partial = |args| self.some_method(args);

but that won't fit into a fn pointer, for the reasons explained above. Nevertheless, it is callable (obviously), so you'll only lose the ability to have the specific fn pointer type; you won't lose any essential functionality.

2 Likes

Thank you for your additional comments. To be honest, I'm still struggling to get my head around closures and, in particular, how to apply your suggestion to building a jump table inside a struct. I'll mess with a bit and maybe that will help me understand how closures work and under what circumstances one would use them. Again, I'm grateful for all the help.

You can mostly think of a closure as a compiler generated struct, where the fields are its captures. The struct is constructed at the closure definition site and implements some Fn* traits, as well as a handful of other traits such as Clone when possible.

    let gets_consumed = "Hello".to_string();
    let gets_borrowed = Some(0);
    let closure = || {
        // Must capture `gets_consumed` by value
        drop(gets_consumed);
        // Only need capture `gets_borrowed` by shared reference
        println!("{gets_borrowed:?}");
    };
    
    (closure.clone())();
    closure();
struct Closure<'a> {
    gets_consumed: String,
    gets_borrowed: &'a Option<i32>,
}

impl Clone for Closure<'_> { ... }
impl FnOnce<()> for Closure<'_> { ... }
    let gets_consumed = "Hello".to_string();
    let gets_borrowed = Some(0);
    let closure = Closure {
        gets_consumed,
        gets_borrowed: &gets_borrowed,
    };
    
    closure.clone().call_once(());
    closure.call_once(());

There's various nuances around how the compiler infers how to capture things and what traits to implement, as well as other details like "non-capturing closures can coerce to function pointers". But I feel having a general idea of the notional desugaring goes a long way in demystifying closures.

4 Likes

I have no idea what you specifically mean by a jump table and in what way it is special. The example in my previous post shows exactly how to partially apply the self argument if that is what you need. If not, please elaborate.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.