Why can the unsafe function pointer be as the function operand?

Call expressions - The Rust Reference says:

For non-function types, the expression f(...) uses the method on one of the following traits based on the function operand:

The non-function types, as indicated by the link, refer to function item types. In this example:

fn set(i:i32){}
fn main(){
   (&set)(0);  // #1
}

#1 is of type reference to F, where F is a function item type. Therefore, it can be explained by this rule because &F implements Fn* traits. However, consider this example:

unsafe fn foo(i:i32){}
fn main(){
  let f = foo as unsafe fn(i32);
  unsafe{
    f(0);
 }
}

f is of type unsafe function pointer, fn - Rust says:

In addition, all safe function pointers implement Fn, FnMut, and FnOnce, because these traits are specially known to the compiler.

The wording emphasizes safe, in other words, unsafe function pointers don't implement Fn, FnMut, and FnOnce. Furthermore, the (unsafe)function pointers aren't function item types, so why is f(0) is a valid call expression?

The "non-function types" link seems incorrect or unclear to me. I do not think you should interpret it as meaning that function item types are not function types.

function item types are function types, as the link indicated. Did you mean function pointer types are function types?

It seems to me like Fn traits have nothing to do with this example.

The important thing is that f is typed as unsafe fn(i32), so the unsafety is part of the type. It can't be cast into a fn(i32) and doesn't impl Fn(i32).
But you can call it in an unsafe block because (of course) unsafe fn(i32) bindings are callable in that context.

I'm less sure about how this fits into the definitions you cited, hopefully someone can help with that part.

Calling them is a built-in operation. Built-in operations aren't required to use or correspond to a trait implementation. Other pointer types which do not implement Fn(..), such as extern "C" fn(i32), also exist and can be called via a call expression.

1 Like

The cited rules are all I can find in the reference about call expressions. In terms of the content in the link, a function pointer type is not a function type, so the rule does not apply to function pointer types. So, this is the point of why I asked the question about why the unsafe function pointer can be used as the function operand in a call expression.

I agree that function pointers should also be mentioned in this part of the reference. (And that the link should be edited so that the link to the definition of function types does not have the text "non-function types".)

I agree with @quinedot the link should not have the "non-function types" text, I guess it's probably intended to be "non-<function types>", i.e. put the link under the "function types".

but also, the "reference" should have a more straight and precise definition of what "function types" are.

in simple terms, function items and function pointers are function types, both safe and unsafe. they are stateless, and they just resolve to an address. calling a function is a fundamental operation of a programming language and the underlying CPU too.

the non-function types refer to closures, which are user defined types (but not with the struct {} or enum {} syntax, but with the |arg| expr syntax). they are generally stateful (they have a self receiver), the function call operator are just syntax sugars for self.call(...), self.call_mut(...), or self.call_once(...). you can think of them like a special case of the overloadable operators in core::ops, analog to C++'s operator (), but they are so special in rust that the non-sugar syntax remains unstable for very very long (I think they may be permanent unstable, but I'm not sure).

for practical reasons, safe function types also implement the Fn* traits, and vice versa, capture-less closures can be coerced into function pointers. these are ergonomic features of the language though, and are separate from the definition of function vs non-function types.

without these conversions, you can still write the equivalent code, just less convenient. for example, if (safe) functions didn't implement Fn*, you must manually write a "shim" closure everytime you want to pass a function as argument where a parameter of type impl Fn is expected. if capture-less closures were not coerced into function pointers, you need to define a named function for every callback, even if it were used only once.

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.