2

This question is following up from this SO question.

As a novice in Rust, I am trying to understand the way a function pointer (fn) is initialized. Here's an abridged and much simpler description of the application I am writing.

A Controller is initialized with a route: i32 value. Later, this value may be modified and all the old values are stored in a list, as a history of changes.

Here's a sample 'lib.rs` content:

use futures::future;
use random_number::random;



pub struct Controller {
    pub route: i32,
    pub running_history_modified_routes: Vec<i32>
}

impl Controller {

     // a 'route' is initialized with an integer
     pub fn new(p: i32) -> Controller {
         Controller { route: p, running_history_modified_routes: Vec::new()}
     }

     // Subsequently, the 'route' may get a new value.
     // Here, we are generating a random new value and storing that in
     // vector, as history of changes to route.
     pub fn compute_a_new_route (&mut self, using_seed: i32) -> &mut Controller {

            // My confusion is around this initialization!
            let as_function_pointer: fn(i32) -> i32 = free_function_generate_random_route_value;

            let a_fresh_route = self.get_a_route_afresh(using_seed,as_function_pointer);
            self.running_history_modified_routes.push(a_fresh_route);
            self

     }

     fn get_a_route_afresh(&self, seed_as_higher_limit: i32, f:fn(i32) -> i32) -> i32 {
             f(seed_as_higher_limit)
     }

     fn method_generate_random_route_value(&self,seed_as_higher_limit: i32) -> i32 {
         random!(0 as i32, seed_as_higher_limit)
     }

    fn assoc_function_generate_random_route_value(seed_as_higher_limit: i32) -> i32 {
        random!(0 as i32, seed_as_higher_limit)
    }
}

fn free_function_generate_random_route_value(seed_as_higher_limit: i32) -> i32 {
    random!(0 as i32, seed_as_higher_limit)
}

fn get_a_route_afresh(..) receives a function pointer and calls it to get the new route value (which is a random number, for this example, of course :-) ).

I have three different candidates for the function pointer (commented in the code above):

  • Controller's implementation method method_generate_random_route_value
  • Controller's associated function assoc_function_generate_random_route_value
  • Module's free function free_function_generate_random_route_value

My understanding is that each of these can be used for initializing a function pointer before calling self.get_a_route_afresh(using_seed,as_function_pointer), in the same way! But, the compiler disagrees when I do this:

let as_function_pointer: fn(i32) -> i32 = self.method_generate_random_route_value;

and tells me this:

error[E0615]: attempted to take value of method `method_generate_random_route_value` on type `&mut Controller`
  --> src/lib.rs:20:60
   |
20 |             let as_function_pointer: fn(i32) -> i32 = self.method_generate_random_route_value;
   |                                                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method, not a field
   |
help: use parentheses to call the method
   |
20 |             let as_function_pointer: fn(i32) -> i32 = self.method_generate_random_route_value(_);
   |                                                                                              +++

Of course, when I use the associated or free function, both the compiler and I, are happy.

What is it that I am missing about a (impl) method's applicability when a function pointer is expected?

Here's Cargo.toml:

[package]
name = "FutureExperiment"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
random-number = "0.1.8"


[lib]
name="function_pointers_experiment"
path="src/lib.rs"
Nirmalya
  • 717
  • 9
  • 19
  • 1
    Unlike e.g. python rust doesn't have "instance method" values, `a.b` on a method is an error as you can see in your code. Because what Rust actually does is desugar to a "method call" to a static function call with the instance as first parameter e.g. `a.b()` is really `A::b(a)`. Therefore you can't take an instance method as a value. And if you could it would not be a *function pointer*: a function pointer is a free function with no associated data, but an instance method object would need to store the instance. – Masklinn Feb 26 '23 at 08:26
  • Thanks @Masklinn, for that brief yet clear explanation.. I should have been able to see that aspect of _desugaring_ . I had been a bit confused because of my background in Scala/Java, where an instance-level method can be used when a Method reference is expected. – Nirmalya Feb 26 '23 at 10:50
  • 1
    FWIW you can take `Controller::get_a_route_afresh` as a value, and it'll be coercible to an `fn`, but that'll be an `fn(&Controller, i32) -> i32`, not just an `fn(i32) -> i32`, similar to taking a method reference on a type in Java. There is no instance method references, you have to use an explicit closure in order to capture `self`. – Masklinn Feb 26 '23 at 12:32
  • Yes! Got that. @Masklinn – Nirmalya Feb 26 '23 at 13:38

1 Answers1

0

A function that binds self is not a function pointer. A function pointer fn (with a lowercase 'f') is a pointer to a top-level Rust function that does not close around any variables. Once you want to close around variables (even self), you no longer have a function pointer. Instead, you have a closure Fn (or FnOnce or FnMut, depending on how you close around variables). Top-level functions are function pointers, and associated functions (without a self binding) are really just top-level functions by another name.

If you want to accept any callable object, FnMut is a good place to start. Use Fn (with a capital 'F') if you need to call it concurrently (since Fn is forbidden from taking mutable references, it's safe to share in multiple places at once), or FnOnce if you need to take ownership of some values you can only use once. The type fn of function pointers is mainly used for low-level techniques that require an actual concrete pointer to some function, such as when interfacing with C code that doesn't know what a closure is.

Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116
  • The fact that 'self.method()' is a kind-of closure around 'self', is quite a conceptual shift, to me. Many thanks. That clarifies a lot. Thanks also for mentioning the Fn* family; I am familiar with them. Because of my background of C (and Scala/Java), a function pointer sits pretty well with my thinking. That's the reason, I have been exploring the fn of Rust here (as opposed to Fn). – Nirmalya Feb 26 '23 at 10:42