Function pointers and mutability

In the following code, are my only options for putting foo2 into f:
a) Change the signature of foo2
b) Wrap foo2 in another method with the correct signature

fn main() {
    let mut s = S;
    let mut f: fn(&mut S);

    f = S::foo;
    f = S::foo2;
}

struct S;

impl S {
    fn foo(&mut self) {}

    fn foo2(&self) {}
}

In reality I have more than two functions and most mutate the argument, but one particular method is degenerate and does not. I dislike having to say it does as these methods are used directly elsewhere, but I have a need to store it in the common pointer.

What issues would it cause if a function taking &self could be treated as taking &mut self for use in a function pointer?

You can do this:

    f = |s| S::foo2(s);
1 Like

Anything, since nothing guaranteed. You can store some wrapper function instead though.

fn main() {
    let mut s = S;
    let mut f: fn(&mut S);

    f = S::foo;
    f = |s| s.foo2();
}
1 Like

Thanks both, sorry I wasn't clear. I know that I can do that...wrapping via a closure or named method is essentially the same.

I was wondering two things really:

  1. If there were a way perhaps through type trickery of some kind to avoid the need for the nested method call. My gut feeling is the answer is "no", but it never hurts to ask. I also realize the optimizer will likely remove the nested call, but I try not to rely on that when I don't need to.

  2. What harm if any would there be in allowing a function pointer that expects a &mut self to be compatible with a function taking &self. We already can call &self functions from a mutable reference so it would really just be the flipside of that, unless I am missing something.

OK, you can use std::mem::transmute and enjoy UB:

fn main() {
    let mut s = S;
    let mut f: fn(&mut S);

    f = S::foo;
    f = unsafe { std::mem::transmute::<fn(&S), fn(&mut S)>(S::foo2) } // WARNING: UB!
}

struct S;

impl S {
    fn foo(&mut self) {}

    fn foo2(&self) {}
}

Playground

&mut self is an unique reference, owning reference. There is one and only owning reference at a time.
&self is a NOT unique reference. There are many such a references can be at a time.
Now think about correctness of this code: unsafe { std::mem::transmute::<fn(&S), fn(&mut S)>(S::foo2) }
In a nutshell, this is wrong unless you have ensured that a function will not change self

1 Like

In the example, they got the fn(&self) from a method with no unsafe, so the compiler checked that it can't do anything requiring &mut self. The requirements on using the transmuted function pointer are more strict than the original -- it would definitely be unsound if you let a &mut self method be called with a &self, but this is the opposite.

It's UB in the technical, hasn't-been-decided-yet sense. But I haven't thought of any way it's problematic safety-wise beyond that.

1 Like

In other words, in this case, it's legal, right? Thanks a lot, now I know a little more :slightly_smiling_face:.

As quinedot pointed out, what I am suggesting is the opposite of the scenario you've described.

If I have a function that takes a shared reference I know it won't make modifications.

Storing that in a function pointer that takes an exclusive reference should be safe because at the call site I am guaranteed to get an exclusive reference which can be trivially reborrowed as a shared reference. Now there may be good reasons for not implementing that and I won't pretend to know what they might be, but it should be safe.

1 Like

It's not legal, it's UB. But I haven't thought of a reason it couldn't be legal. Which is what I meant by

However, that doesn't mean you can rely on it before it's supported, if that ever happens. If an optimizer devirtualized and inlined the body of the method while maintaining the semantics of the function pointer, it could observe aliasing in code where that was labeled as impossible, for example. (If it became supported, such optimizations would be invalid.)

I don't know if any such things happen or not today, but I'd just use the closure wrapper anyway. That's definitely defined / supported / legal.

2 Likes

I wasn't considering using unsafe to do this. Just wondering if someone more clever than me knew a way to achieve it (sometimes the stuff people do with types looks like sheer magic), or failing that, musing on the idea of changing it to be legal.

After some refactoring I ended up not needing to do it anyway.

1 Like

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.