I used to believe, in the context of types, Fn/fn were the same thing, i.e.
Fn(i32, i32) -> f32
vs
fn(i32, i32) -> f32
However, they seem to be different things even in the context of types. Where can I read up more on this?
I used to believe, in the context of types, Fn/fn were the same thing, i.e.
Fn(i32, i32) -> f32
vs
fn(i32, i32) -> f32
However, they seem to be different things even in the context of types. Where can I read up more on this?
Got it, so:
Fn
is a trait, fn
is a type.
static functions can be either fn
or Fn
closures can be Fn
, but can not be fn
Are all three above correct?
fn
is a way of introducing a type, in the same way struct
or enum
can. Each function signature is its own type.
Types implement traits, like Fn
, FnMut
, FnOnce
, Copy
, and more. In some cases those impl
's are explicit, in some cases they're inferred or applied automatically, as is the case when closures are used.
When putting bounds on the type of an argument, we can be generic using traits so that any type that matches the trait can be passed.
Not true, if a closure doesn't capture anything ot can be fn
.
fn
is a function pointer, and Fn
is a trait for things that callable from its shared reference. Typically closures capture their environment so they're more than just function pointer. But if they don't, it's just plain function pointer so it can be fn
.
Since one is a type and the other one is a trait,
The former can be used in type position:
//! C-style callbaks
fn call_once (cb: fn())
{
cb();
}
fn call_twice_sequentially (cb: fn())
{
cb();
cb();
}
fn call_twice_in_parallel (cb: fn())
{
::crossbeam::scope(|scope| {
scope.spawn(|_| cb());
scope.spawn(|_| cb());
}).unwrap();
}
The three functions above just take a (function) pointer as argument.
The latter, since it is not a type, can "reach the type realm" through generics (or impl trait
sugar), or trait objects;
//! Rust style "callbacks" for maximum flexibility
fn call_once<Env> (env: Env)
where
Env : FnOnce(), // input environment is callable by value (thus consuming it)
{
env();
}
fn call_twice_sequentially<Env> (mut env: Env)
where
Env : FnMut(), // input environment is callable by (unique) reference
{
env(); // first borrow begins and ends here: &mut possible
env(); // second borrow begins and ends here
}
fn call_twice_in_parallel<Env> (env: Env)
where
Env : Fn(), // input environment is callable by (shared) reference
Env : Sync, // environment can be *shared* across thread boundaries.
{
::crossbeam::scope(|scope| { // in parallel, do:
scope.spawn(|_| env()); // one shared borrow here
scope.spawn(|_| env()); // another one here
}).unwrap(); // both borrows end here
}
The three functions above can be monomorphized into taking any kind of environment as argument, so as long as that environment is callable: a closure.
Finally, a function pointer cannot have a captured environment, thus
fn()
= "empty" (zero-sized) environment : Fn
In other languages Fn
would be called a Callable
interface. fn
is just a function type.
Another way to think about it is like this:
Fn*
is a trait and is an operation. Meaning that this is a syntactic sugar trait, like Add
, Mul
, Sub
, or Div
, or even Deref
and DerefMut
. The sugar that this trait applies is the ability to be called like so:fn foo(data: impl Fn<(usize, ), Output=usize>) {
println!("{}", data());
}
fn
pointers are just pointers to function. Not to be thought of as objects which just so happen to implements a Fn*
trait, they are solely function pointers. IE, they point to the entrance of a function and that's it. (And yes, they are technically just references which implement the Fn*
traits...)fn foo(data: fn(usize) -> String) {
println!("The result is {}", data(30));
}
fn double_and_fmt(num: usize) -> String {
format!("{}", num * 2)
}
fn main() {
foo(double_and_fmt);
foo(|x| "This is a closure".to_string());
let x = 3;
foo(move |z| {format!("{}", z + x)}; //This won't work.
}
And the last line in main
will not work because it now is an object with a state, not just a function pointer. The call to foo
with a closure that doesn't move
is usually done like this internally:
fn __closure_at_line_col(x: usize) -> String {
"This is a closure".to_string()
}
fn main() {
// -- snip --
foo(__closure_at_line_col);
// -- snip --
}
I'm surprised nobody has mentioned this, but Fn
actually is also a type. All traits are. However, spelling them this way is deprecated; the proper name for it is dyn Fn
.
// These are equivalent.
type OldSyntax = Fn(usize) -> i32; // this is deprecated
type NewSyntax = dyn Fn(usize) -> i32;
dyn Trait
is what we call a trait object; it's basically a type with a vtable for the purposes of dynamic polymorphism. This type is "unsized" and you can only refer to it behind a pointer. (e.g. Box<dyn Trait>
or &dyn Trait
)
This type should seldom show up in public interfaces (it is mostly useful for functions stored in private fields), but it does have some niche use cases. (e.g. to make a function monomorphic, or to prevent an infinite type from appearing in a recursive function)
It would be nice to stop teaching the old spelling entirely so new people don't get confused on when to use the syntax. It can still be documented somewhere, but not taught. Also the old spelling will eventually become a compiler error, so we should start transitioning away from the old spelling.
A post was split to a new topic: Difference between fn
and Box<dyn Fn>