Closure parameter type deduction does not work when wrapped in something

Consider the following code snippet:

fn set_callback<F>(_: F)
where
    F: FnOnce(&mut [u8])
{ }

fn foo() {
    set_callback(|buf| buf[0] = 0);
}

It compiles. The compiler can deduce the type of buf to &mut [u8] according to the trait bound on F.

However, if you change the parameter type of set_callback from F to Option<F>, things get different:

fn set_callback<F>(_: Option<F>)
where
    F: FnOnce(&mut [u8])
{ }

fn foo() {
    set_callback(Some(|buf| buf[0] = 0));
}

It fails to compile because compiler cannot deduce the type of buf anymore:

error[E0282]: type annotations needed
 --> src/lib.rs:7:24
  |
7 |     set_callback(Some(|buf| buf[0] = 0));
  |                        ^^^ consider giving this closure parameter a type
  |
  = note: type must be known at this point

For more information about this error, try `rustc --explain E0282`.
error: could not compile `playground` due to previous error

What is really happening when compiler tries to deduce the types of closure parameters? Is it unsound to deduce the type of buf to &mut [u8] when the closure is wrapped in something like Option?

1 Like

I don't think it would be unsound(in this case), but the compiler (apparently) has limits on how it infers types.

It says here:

"The type inference is based on the standard Hindley-Milner (HM) type inference algorithm, but extended in various way to accommodate subtyping, region inference, and higher-ranked types."

which may give some clue as to what is happening.

This type of inference failure seems to commonly happen when the type is needed to call a method or similar, and that context doesn't constrain the closure well enough. In this case, the type is needed to know what type of Option<T> to instantiate. It's not a soundness issue.

You could have a

fn set_callback_to<F>(f: F)
where
    F: FnOnce(&mut [u8])
{ 
    set_callback(Some(f))
}

to work around it. (Or a free function fn(F) -> Option<F>.)

Also, for this particular example, note that you can't pass None and have things work either. That would be true whatever the bounds on Option<SomeGenericParameter>, but you can't name closure types, so you can't annotate the None to instantiate F to be a closure type. And if you're storing a callback, you're probably boxing it up within your struct anyway, so perhaps have a method that takes a Option<Box<dyn FnOnce(...)>>.

(But I recognize it's just a minimal example.)

Thanks for your reply. But I still wonder why compiler cannot deduce the type of buf, or do we have RFCs yet to improve this?

The underlying theoretical ground of the type inference should be just fine. I agree with you that this should be compiler implementation limits.

It's way beyond my knowledge and understanding to give you a definitive answer, but my very vague, fuzzy understanding would be that when you get into areas like closures, function pointers and enums where there could be any number of potential matches, you are reaching the limits of reasonable type inference. I doubt this is a bug or anything, sometimes in these areas you simply DO have to supply more type information. There might be some RFC proposal to change this, but I somewhat doubt it. Perhaps an expert can explain more clearly ( I am not one on this subject ).

1 Like

This is probably considered part of #12679. In a different inference bug, ExpHp says:

the compiler has a special case when type checking closures that appear as function arguments (it will infer types from Fn bounds). So this problem (and many other related problems with type inference) tends to rear its ugly head only in comparatively uncommon scenarios, such as when assigning a closure to a local variable with let .

As far as I know there's nothing blocking a PR other than the difficulty of writing it (but I, too, am not an expert on compiler internals). That issue is almost 8 years old and predates stable Rust.

1 Like