Function pointers to members of self

Is it possible to pass a function pointer which points to a member of self or &self (or ...)?

I suspect you need a closure, but can you offer an example of what you want?

I have an overarching Server type, which within it, contains several modules. One submodule, A, contains indexed elements called Foo (an Arc smart pointer), and the other submodule, B, needs access to specific indexes on-command. The Server type is not wrapped in a smart-pointer, and as such, cannot be passed a version of itself to the A. For this reason, I am looking to just pass a function pointer with a pointee equal to a function Server::get_foo() -> Foo. I am willing to write a little bit of unsafe code, because the Server type if not meant to be moved anywhere in memory

No, you will need a closure. Function pointers can only acces function locals and globals, and self is neither. You can use &mut FnMut() if you really need a pointer.

If the function doesn't actually mutate any data (only fetches data from a hashmap), wouldn't a plain-old Fn work?

Yes, in that case you can use &Fn()

1 Like
impl<'cxn, 'driver: 'cxn, 'server: 'driver> Server<'cxn, 'driver, 'server> where Self: 'server {
    /// Creates a new server instance
    pub async fn new<'a: 'server>(thread_pool: &'a mut ThreadPool) -> Server<'cxn, 'driver, 'server> {
        let local_network_account = load_node_nac().await;
        let network_map = load_local_network_map(&local_network_account).await;
        let stage_driver_tubes = unbounded();
        let stage_driver = StageDriver::new(stage_driver_tubes.0);

        let mut this = Self {stage_driver, stage_driver_tube_rx: stage_driver_tubes.1, network_map, connections: HashMap::default(), associated_nacs: HashMap::default(), local_network_account, thread_pool, init_time: Instant::now()};
        let this_ref = &this;
        // In order for the stage driver to function, we must give it a function for which it can retrieve drills on command.
        // It only needs to be an Fn, because we are not mutating any data. We use `move` to move `this_ref` into the closure
        this.stage_driver.drill_getter.write(Box::new(move |cid, drill_version_opt| {
            this_ref.get_drill(cid, drill_version_opt)
        }));

        this
    }

    fn get_drill(&self, cid: u64, drill_version_opt: Option<u32>) -> Option<&'driver Drill<dyn DrillType>> {
        match self.connections.get(&cid) {
            Some(sess) => {
                if let Some(drill_version) = drill_version_opt {
                    sess.client.read().toolset.get_drill(drill_version)
                } else {
                    sess.client.read().toolset.get_most_recent_drill()
                }
            }, 
            None => {
                None
            }
        }
    }

    /// Used for network synchronization. Within any HyperVPN, the time for each client is determined by the time at
    /// the central node. "Server time" is just the relative offset of time since the server beagn
    pub fn get_server_time(&self) -> f32 {
        self.init_time.elapsed().as_secs_f32()
    }
}

Thanks for the replies. Does the constructor look valid to you? Particularly, I am concerned about this_ref being moved into the box'ed closure and then having to return this at the end of the constructor's closure.

Here's a generic playground version of the problems my approach encounters:

You'll have to use raw pointers across the closure boundry to appease the borrow checker, and you should return Pin<Box<Self>> from new to guarantee that it is impossible to invalidate that pointer.

1 Like

That indeed works. For educational purposes, I asked: "what if, instead of a box, I Pin a wrapper type that deref's and deref_mut's into the inner type to satisfy the requirement for Pin?

Why might the println! not show the correct value, while the return value of the function works?

I am still working on deducing the interconnection between Pin and Box. Perhaps, to stay away from Boxes, I can use your RelPtr crate?

Also, by boxing a high-level type, does that imply EVERYTHING recursively inside the structure is boxed all the way down to the low-levels too?

(I'm thinking YES, although, if there are stack pointers, then those pointees are not on the heap)

Try it in release mode.

RelPtr is not that easy to use, but if you know that everything moves together, then it should be fine. In this case, referencing a field with a RelPtr should be fine.

It will box everything that is stored inline the struct, so any pointees will not be boxed, but the pointers will be boxed.

Yeah i noticed that in release mode, I get the exact value. I thought that it was an anomaly or that the compiler automatically "knew" what I meant, and reduced to code to its simplest expression. What magic is happening there?

Things should move together if they are in the same structure, right?

Yes

Note, you don't even need release mode to see the UB playground, just adding a print statement will suffice.

You are invoking UB. When you move the newly created value out of new you are invalidating this_ref. Remember, Pin is not a magic type, it is literally defined like this,

struct Pin<P> {
  pointer: P
}

(the only reason it has a #[lang] attribute is so that the compiler can use it to do async/await, not to give it special behavior),

  1. Tmp should be !Unpin because you are making a self-referential type.
  2. This will force you to use Pin::new_unchecked to construct the pin, as per the docs,

If the constructed Pin<P> does not guarantee that the data P points to is pinned, that is a violation of the API contract and may lead to undefined behavior in later (safe) operations.

Your type TmpHolder does not guarantee that Pin<TmpHolder> will not Tmp will not move until it is dropped. Therefore, you are invoking UB right there.


Before creating self-referential types like this, carefully read through the docs, because Pin is just a clever use of unsafe to get the desired properties.

Having a Pin means that someone has done the check to make sure that the pointee is immovable until it drops. This someone will have to be you if you are writing the self-referential type.

4 Likes

For extended discussion, there was something that I was uncertain as to how the internals operate. From this thread, earlier, you helped me come to the conclusion that I should use a constant pointer to a heap-pinned T as such:

let mut this = unsafe { Pin::new_unchecked(Box::new(Self {stage_driver, stage_driver_tube_rx: stage_driver_tubes.1, network_map, connections: HashMap::default(), associated_nacs: HashMap::default(), cnacs_loaded: cnacs.into(), local_network_account, thread_pool, init_time: Instant::now()})) };
let this_ptr = &*this.as_ref() as *const Self;
this.stage_driver.server_ptr.write(this_ptr); 
Ok(this)

Pinning something to the stack only makes sense in the singular frame that is implied by the closure (e.g., using pin_mut! for polling futures that require self: Pin<&mut Self>). So, nothing crazy there. So long as I'm not referencing the pin's pointee outside of the pin's created closure, there wont be UB.

But, pinning something to the heap... I suppose I just lack understanding there. Whereas the stack occupies the L1/Lx caches, the heap is way out in RAM. Are pointees pinned to the heap independent of closures (it seems like they are, but I feel like I'm missing something in this elaboration of my understanding)?

As a side note, you don't need an unsafe here

3 Likes

Storing a heap-pinned T inside a hashmap, and then passing-around a constant pointer to T should guarantee that the T within its Pin<Box<T>> is immovable (so long as pin contract isn't violated), yeah?

Yes, using Pin<Box<T>> will guarantee that T is immovable, as long as no one violates those guarantees by using unsafe.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.