Write fix point function in Rust

后端 未结 4 1464
醉梦人生
醉梦人生 2021-01-11 13:42

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         


        
4条回答
  •  生来不讨喜
    2021-01-11 14:10

    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) {guess_loop::>}, 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.

提交回复
热议问题