andy_xor_andrew
14 days ago
if I'm not mistaken (and I very well may be!) my primary confusion with closures comes from the fact that: the trait they implement (FnOnce / Fn / FnMut) depends entirely upon what happens inside the closure.
It will automatically implement the most general, relaxed version (FnMut I think?) and only restrict itself further to FnOnce and Fn based on what you do inside the closure.
So, it can be tricky to know what's going on, and making a code change can change the contract of the closure and therefore where and how it can be used.
(I invite rust experts to correct me if any of the above is mistaken - I always forget the order of precedence for FnOnce/Fn/FnMut and which implies which)
bobbylarrybobby
14 days ago
The three Fn* types correspond to the three ways you can refer to a value: &T, &mut T, T. Fn captures its environment by shared reference, FnMut by exclusive reference, and FnOnce by value, and everything flows from that. Calling a Fn is the same as using a reference. Calling a FnMut is the same as using a mutable reference (you can do it as many times as you want but no two uses may overlap in time). And calling a FnOnce is the same as moving a value (you can do it at most once).
umanwizard
14 days ago
The least restrictive for the caller is Fn (you can call it whenever), then FnMut (you can call it only if you have exclusive access to it, as many times as you want), then FnOnce (you can call it only if you have exclusive owned access, and calling it once destroys it).
The least restrictive for the function itself is the opposite order: FnOnce (it can do anything to its environment, including possibly consuming things without putting them back into a consistent state), followed by FnMut (it has exclusive access to its environment, and so is allowed to mutate it, but not destroy it), followed by Fn (it has only shared access to its environment and therefore is not allowed to mutate it).
Since these orders are inverses of each other, functions that are easier to write are harder to call and vice versa. That’s why they implement the trait with the minimum amount of power possible, so that they can be called in more places.
yoshuaw
14 days ago
> I always forget the order of precedence for FnOnce/Fn/FnMut
The way I remember the ordering is by thinking about the restrictions the various Fn traits provide from a caller's perspective:
1. FnOnce can only ever be called once and cannot be called concurrently. This is the most restrictive.
2. FnMut can be called multiple times but cannot be called concurrently. This is less restrictive than FnOnce.
3. Fn can be called multiple times and can even be called concurrently. This is the least restrictive.
So going from most to least restrictive gives you `FnMut: FnOnce` and `Fn: FnMut`.umanwizard
14 days ago
Fn can only be called concurrently if its environment is Sync, which is often true but not necessarily.
It’s more precise to say that Fn can be called even when you only have shared access to it, which is a necessary, but not sufficient, condition for being able to be called concurrently.
pwdisswordfishy
13 days ago
I'm not sure myself, does Fn correspond to reentrancy or is there some detail I am missing?
armchairhacker
13 days ago
`Fn`, `FnMut`, and `FnOnce` can also implement and not implement `Sync` (also `Send`, `Clone`, `Copy`, lifetime bounds, and I think `use<...>` applies to `impl Fn...` return types).
EDIT: https://news.ycombinator.com/item?id=46750011 also mentioned `AsyncFn`, `AsyncFnMut`, and `AsyncFnOnce`.
csomar
13 days ago
My issue is that there is no easy way to know the signature generated by the closure (unlike a function) until you read the compiler errors and even then it's some cryptic mess because closures are anonymous structs. Or maybe I missed some LSP config/extension?
krukah
14 days ago
Easiest mnemonic to remember precedence is simply ordering by the length of their names.
FnOnce
FnMut
Fn
KolmogorovComp
14 days ago
This is correct. But it’s not really surprising, it’s type inference.
gpm
14 days ago
It isn't really type inference. Each closure gets a unique type. Rather it's an automatic decision of what traits (think roughly "superclasses" I guess if you aren't familiar with traits/typeclasses) to implement for that type.
chowells
13 days ago
So you're saying... it's type inference of type classes, just like in Haskell?
gpm
13 days ago
No, I don't think so, not unless there's some feature of Haskell type classes I'm completely unaware of.
If anything it's closer to SFINAE in C++ where it tries to implement methods but then doesn't consider it an error if it fails. Then infers type-classes based on the outcome of the SFINAE process. Or the macro analogy another poster made isn't bad (with the caveat that it's a type system aware macro - which at least in rust is strange).
csomar
13 days ago
I am not sure how Haskell works but I think what the previous poster meant is that the types get determined at compile time. Closures are akin to macros except you can't see the expanded code.
user
14 days ago