I\'ve just started Rust tutorial and ended with such code using recursion
extern crate rand;
use std::io;
use rand::Rng;
use std::cmp::Ordering;
use std::st
Starting at where you left off:
fn fix(func: fn(T, F) -> R) -> F
where F: Fn(T) -> R
{
|val: T| func(val, fix(func))
}
The returned object has an unnameable closure type. Using a generic type won’t help here, since the type of the closure is decided by the callee, not the caller. Here’s where impl
traits come in handy:
fn fix(func: fn(T, F) -> R) -> impl Fn(T) -> R
where F: Fn(T) -> R
{
|val: T| func(val, fix(func))
}
We can’t pass fix(func)
to func
because it expects a nameable type for F
. We’ll have to settle for a trait object instead:
fn fix(func: fn(T, &Fn(T) -> R) -> R) -> impl Fn(T) -> R {
|val: T| func(val, &fix(func))
}
Now it’s time to fight the lifetime checker. The compiler complains:
only named lifetimes are allowed in `impl Trait`, but `` was found in the type `…`
This is a somewhat cryptic message. Since impl traits are always 'static
by default, this is a roundabout way of saying: “the closure does not live long enough for 'static
”. To get the real error message, we append + 'static
to the impl Fn(T) -> R
and recompile:
closure may outlive the current function, but it borrows `func`, which is owned by the current function
So that was the real problem. It is borrowing func
. We don’t need to borrow func
because fn
is Copy
, so we can duplicate it as much as we want. Let’s prepend the closure with move
and get rid of the + 'static
from earlier:
fn fix(func: fn(T, &Fn(T) -> R) -> R) -> impl Fn(T) -> R {
move |val: T| func(val, &fix(func))
}
And voila, it works! Well, almost … you’ll have to edit guess_loop
and change fn(T) -> ()
to &Fn(T) -> ()
. I’m actually quite amazed that this solution doesn’t require any allocations.
If you can’t use impl
traits, you can instead write:
fn fix(func: fn(T, &Fn(T) -> R) -> R) -> Box R>
where T: 'static,
R: 'static
{
Box::new(move |val: T| func(val, fix(func).as_ref()))
}
which is unfortunately not allocation-free.
Also, we can generalize the result a bit to allow arbitrary closures and lifetimes:
fn fix<'a, T, R, F>(func: F) -> impl 'a + Fn(T) -> R
where F: 'a + Fn(T, &Fn(T) -> R) -> R + Copy
{
move |val: T| func(val, &fix(func))
}
In the process of figuring out a solution for your problem, I ended up writing a simpler version of fix
, which actually ended up guide me towards a solution to your fix
function:
type Lazy<'a, T> = Box T + 'a>;
// fix: (Lazy -> T) -> T
fn fix<'a, T, F>(f: F) -> T
where F: Fn(Lazy<'a, T>) -> T + Copy + 'a
{
f(Box::new(move || fix(f)))
}
Here’s a demonstration of how this fix
function could be used to calculate the factorial:
fn factorial(n: u64) -> u64 {
// f: Lazy u64> -> u64 -> u64
fn f(fac: Lazy<'static, Box u64>>) -> Box u64> {
Box::new(move |n| {
if n == 0 {
1
} else {
n * fac()(n - 1)
}
})
}
fix(f)(n)
}