1

I'm trying to update my code for Rust 1.0.alpha, and one section I am having trouble with can be reduced to the below example. I already annotated the closure type, and switched to unboxed closures. However I can't find the correct type for fun. I tried fun : FnMut() -> IoResult<u32> but even though the whole point of FnMut, FnOnce, and friends is to provide traits for closures to fulfill; the compiler can't seem to match the types properly.

I've read the following:

but they don't explain clearly how to deal with this issue

use std::io::File;
use std::io::IoResult;
use std::io::fs::PathExtensions;
use std::iter::range_step;

fn main() {
    let path = Path::new("fid");
    let mut file = File::open(&path);
    let big = true;
    let mut v = vec![];
    let fun = if big {
        |&mut:| file.read_be_u32()
    } else {
        |&mut:| file.read_le_u32()
    };
    for _ in range_step(0u64, path.stat().unwrap().size,4u64){
        v.push(fun().unwrap());
    }
    println!("{}",v);
}

This gives:

scratch.rs:11:15: 15:6 error: if and else have incompatible types: expected `closure[scratch.rs:12:9: 12:35]`, found `closure[scratch.rs:14:9: 14:35]` (expected closure, found a different closure)

and using fun : FnMut() -> IoResult<u32> or fun : FnMut<(),IoResult<u32>> gives:

scratch.rs:12:9: 12:35 error: mismatched types: expected `core::ops::FnMut() -> core::result::Result<u32, std::io::IoError>`, found `closure[scratch.rs:12:9: 12:35]` (expected trait core::ops::FnMut, found closure)
scratch.rs:12         |&mut:| file.read_be_u32()
                      ^~~~~~~~~~~~~~~~~~~~~~~~~~
scratch.rs:14:9: 14:35 error: mismatched types: expected `core::ops::FnMut() -> core::result::Result<u32, std::io::IoError>`, found `closure[scratch.rs:14:9: 14:35]` (expected trait core::ops::FnMut, found closure)
scratch.rs:14         |&mut:| file.read_le_u32()
                      ^~~~~~~~~~~~~~~~~~~~~~~~~~
Community
  • 1
  • 1
Camden Narzt
  • 2,271
  • 1
  • 23
  • 42

3 Answers3

4

Here's Shepmaster's answer without Box:

use std::io::{File,IoResult};
use std::iter::range_step;

fn main() {
    let path = Path::new("fid");
    let mut file = File::open(&path);
    let big = true;
    let mut fun_be;
    let mut fun_le;
    let mut fun: &mut FnMut() -> IoResult<u32> = if big {
        fun_be = |&mut:| file.read_be_u32();
        &mut fun_be as &mut FnMut() -> _
    } else {
        fun_le = |&mut:| file.read_le_u32();
        &mut fun_le as &mut FnMut() -> _
    };

    println!("{:?}", fun())
}
Community
  • 1
  • 1
Francis Gagné
  • 60,274
  • 7
  • 180
  • 155
  • Nice! My train of though stopped on one uninitialized variable (which obviously won't work) and refused to go further :) – Vladimir Matveev Jan 11 '15 at 21:05
  • Yeah, we need 2 different variables, because we have 2 closures with 2 different types! This trick can also be used with other traits in general, not just with closures. – Francis Gagné Jan 11 '15 at 21:15
  • Nice, but since I don't see any negative side effects from the boxed version, I'll stick with that. – Camden Narzt Jan 11 '15 at 21:43
  • 1
    @CamdenNarzt: this is a slightly more efficient form as it doesn’t involve a dynamic allocation. It takes a little more stack space, but avoids an allocation. When it comes to micro-optimisation, this is the place to be ☺ – Chris Morgan Jan 12 '15 at 04:36
2

Where you are dealing with pre-existing functions, you don’t actually need closures at all; you can work directly with the functions, like this:

use std::io::{File,IoResult};
use std::iter::range_step;

fn main() {
    let path = Path::new("fid");
    let mut file = File::open(&path);
    let big = true;
    let fun: fn(_) -> _ = if big {
        Reader::read_be_u32
    } else {
        Reader::read_le_u32
    };

    println!("{:?}", fun(&mut file))
}

(The : fn(_) -> _ is, alas, necessary. I’m not sure whether it will become unnecessary at some point or not.)

Chris Morgan
  • 86,207
  • 24
  • 208
  • 215
  • For anyone who like me misses it the first time: as shown in the answer snippet you can pass the object a method is targeting to the method in this manner, and that's why the closure isn't necessary. – Camden Narzt Jan 13 '15 at 03:15
0

Previously, closures would be automatically boxed, but now we have unboxed closures. This is a good thing from an efficiency perspective!

However, in your case, you want to have a binding that implements a trait but we don't care about the actual type of the variable. In this case, the trait is FnMut, and the concrete types are automatically generated types, unique to each closure. To do this, we need to have a trait object (like &Trait or Box<Trait>). Here's an example where we re-box the unboxed closures, creating trait objects:

use std::io::{File,IoResult};
use std::iter::range_step;

fn main() {
    let path = Path::new("fid");
    let mut file = File::open(&path);
    let big = true;
    let mut fun: Box<FnMut() -> IoResult<u32>> = if big {
        Box::new(|&mut:| file.read_be_u32())
    } else {
        Box::new(|&mut:| file.read_le_u32())
    };

    println!("{:?}", fun())
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • BTW, to lessen the boilerplate you can write `Box IoResult>` only once: `let mut fun: Box IoRsult> = ...` and then omit `as` casts. – Vladimir Matveev Jan 11 '15 at 18:52
  • Ideally such thing wouldn't require boxing and heap allocation. Stack-based closures would be more than enough, as old closures demonstrate it. However, I couldn't find a proper way to conditionally construct different `&mut`-based trait objects :( – Vladimir Matveev Jan 11 '15 at 18:54
  • @VladimirMatveev thanks! I got bit by a compiler bug before where that [caused an ICE](https://github.com/rust-lang/rust/issues/20097). Maybe it's fixed now! – Shepmaster Jan 11 '15 at 18:54
  • @VladimirMatveev I was trying the exact same thing... `file` gets borrowed multiple times. – Shepmaster Jan 11 '15 at 18:55
  • Yeah, and if they are created in branches themselves they are destroyed too soon... – Vladimir Matveev Jan 11 '15 at 18:56