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
This can be done at zero runtime cost if you're willing to use unstable features (i.e. a nightly compiler) and willing to... obfuscate your code slightly.
First, we need to turn the result of fix
into a named struct. This struct needs to implement Fn
, so we'll implement it manually (this is an unstable feature).
#![feature(fn_traits)]
#![feature(unboxed_closures)]
extern crate rand;
use rand::Rng;
use std::cmp::Ordering;
fn try_guess(guess: T, actual: T) -> bool {
match guess.cmp(&actual) {
Ordering::Less => {
println!("Too small");
false
}
Ordering::Greater => {
println!("Too big");
false
}
Ordering::Equal => {
println!("You win!");
true
}
}
}
struct Fix
where F: Fn(i32, &Fix)
{
func: F,
}
impl FnOnce<(i32,)> for Fix
where F: Fn(i32, &Fix)
{
type Output = ();
extern "rust-call" fn call_once(self, args: (i32,)) -> Self::Output {
self.call(args)
}
}
impl FnMut<(i32,)> for Fix
where F: Fn(i32, &Fix)
{
extern "rust-call" fn call_mut(&mut self, args: (i32,)) -> Self::Output {
self.call(args)
}
}
impl Fn<(i32,)> for Fix
where F: Fn(i32, &Fix)
{
extern "rust-call" fn call(&self, (val,): (i32,)) -> Self::Output {
(self.func)(val, self);
}
}
fn fix(func: F) -> Fix
where F: Fn(i32, &Fix)
{
Fix { func: func }
}
fn guess_loop(actual: i32, recur: &F)
where F: Fn(i32)
{
let guess_int = rand::thread_rng().gen_range(1, 51);
if guess_int != actual {
recur(actual)
}
}
fn main() {
let secret_number = rand::thread_rng().gen_range(1, 51);
fix(guess_loop)(secret_number);
}
However, we're not done yet. This fails to compile with the following error:
error[E0281]: type mismatch: the type `fn(i32, &_) {guess_loop::<_>}` implements the trait `for<'r> std::ops::Fn<(i32, &'r _)>`, but the trait `for<'r> std::ops::Fn<(i32, &'r Fix}>)>` is required (cyclic type of infinite size)
--> src/main.rs:77:5
|
77 | fix(guess_loop)(secret_number);
| ^^^
|
= note: required by `fix`
Note: In case you're not aware, in Rust, each function has its own, zero-sized type. If a function is generic, then each instantiation of that function will have its own type as well. For example, the type of guess_loop::
will be reported by the compiler as fn(i32, &X) {guess_loop::
(as you can see in the error message above, except with underscores where the concrete type hasn't been resolved yet). That type can be coerced to a function pointer type implicitly in some contexts or explicitly with a cast (as
).
The problem is that, in the expression fix(guess_loop)
, the compiler needs to instantiate guess_loop
, which is a generic function, and it looks like the compiler isn't able to figure out the proper type to instantiate it with. In fact, the type we would like to set for type parameter F
references the type of guess_loop
. If we were to write it out in the style reported by the compiler, the type would look like fn(i32, &Fix
, where X
is replaced by the type itself (you can see now where the "cyclic type of infinite size" comes from).
We can solve this by replacing the guess_loop
function by a non-generic struct (we'll call it GuessLoop
) that implements Fn
by referring to itself. (You can't do this with a normal function because you can't name a function's type.)
struct GuessLoop;
impl<'a> FnOnce<(i32, &'a Fix)> for GuessLoop {
type Output = ();
extern "rust-call" fn call_once(self, args: (i32, &Fix)) -> Self::Output {
self.call(args)
}
}
impl<'a> FnMut<(i32, &'a Fix)> for GuessLoop {
extern "rust-call" fn call_mut(&mut self, args: (i32, &Fix)) -> Self::Output {
self.call(args)
}
}
impl<'a> Fn<(i32, &'a Fix)> for GuessLoop {
extern "rust-call" fn call(&self, (actual, recur): (i32, &Fix)) -> Self::Output {
let guess_int = rand::thread_rng().gen_range(1, 51);
if !try_guess(guess_int, actual) {
recur(actual)
}
}
}
fn main() {
let secret_number = rand::thread_rng().gen_range(1, 51);
fix(GuessLoop)(secret_number);
}
Notice that GuessLoop
's implementation of Fn
is no longer generic on the type of the recur
parameter. What if we tried to make the implementation of Fn
generic (while still leaving the struct itself non-generic, to avoid cyclic types)?
struct GuessLoop;
impl<'a, F> FnOnce<(i32, &'a F)> for GuessLoop
where F: Fn(i32),
{
type Output = ();
extern "rust-call" fn call_once(self, args: (i32, &'a F)) -> Self::Output {
self.call(args)
}
}
impl<'a, F> FnMut<(i32, &'a F)> for GuessLoop
where F: Fn(i32),
{
extern "rust-call" fn call_mut(&mut self, args: (i32, &'a F)) -> Self::Output {
self.call(args)
}
}
impl<'a, F> Fn<(i32, &'a F)> for GuessLoop
where F: Fn(i32),
{
extern "rust-call" fn call(&self, (actual, recur): (i32, &'a F)) -> Self::Output {
let guess_int = rand::thread_rng().gen_range(1, 51);
if !try_guess(guess_int, actual) {
recur(actual)
}
}
}
Unfortunately, this fails to compile with the following error:
error[E0275]: overflow evaluating the requirement ` as std::ops::FnOnce<(i32,)>>::Output == ()`
--> src/main.rs:99:5
|
99 | fix(GuessLoop)(secret_number);
| ^^^
|
= note: required because of the requirements on the impl of `for<'r> std::ops::Fn<(i32, &'r Fix)>` for `GuessLoop`
= note: required by `fix`
Essentially, the compiler is unable to verify that Fix
implements Fn(i32)
, because in order to do that, it needs to verify that GuessLoop
implements Fn(i32, &Fix
, but that is only true if Fix
implements Fn(i32)
(because that impl
is conditional), which is only true if GuessLoop
implements Fn(i32, &Fix
(because that impl
is conditional too), which... you get the idea. In order words, the two implementations of Fn
here are dependent on each other, and the compiler is unable to resolve that.