I tried this code (playground):
fn closure_ret_closure<T: FnOnce() -> T>(f: T) -> T {
f()
}
fn main() {
closure_ret_closure(|| 4);
}
I expected to see this happen:
Either a diagnostic proclaiming that the type parameter for closure_ret_closure is recursive or a diagnostic that clearly states that || 4 does not return FnOnce() -> FnOnce() -> FnOnce() -> ....
Instead, this happened:
Two seemingly conflicting diagnostics are emitted:
error[E0277]: expected a `FnOnce<()>` closure, found `{integer}`
--> src/main.rs:6:5
|
1 | fn closure_ret_closure<T: FnOnce() -> T>(f: T) -> T {
| ------------- required by this bound in `closure_ret_closure`
...
6 | closure_ret_closure(|| 4);
| ^^^^^^^^^^^^^^^^^^^ expected an `FnOnce<()>` closure, found `{integer}`
|
= help: the trait `FnOnce<()>` is not implemented for `{integer}`
= note: wrap the `{integer}` in a closure with no arguments: `|| { /* code */ }`
error[E0308]: mismatched types
--> src/main.rs:6:25
|
6 | closure_ret_closure(|| 4);
| ^^^^ expected integer, found closure
|
= note: expected type `{integer}`
found closure `[closure@src/main.rs:6:25: 6:29]`
The first diagnostic claims that {integer}, the type of the value returned by the closure, is not a FnOnce() -> .... It is not immediately clear that this is in reference to the return type, so the user may believe the value in question is the parameter.
The second diagnostic claims that it expects the parameter to be of type {integer}. This is quite strange, because the previous diagnostic just told us that T: FnOnce() -> FnOnce() -> ..., and {integer} does not fit that requirement.
What may be happening is that since the result of calling || 4 is of type {integer}, the compiler believes T must be {integer}. Since {integer} does not implement FnOnce() -> {integer}, it doesn't fit the bounds on T, generating the first diagnostic. Then, the compiler sees that || 4 is not a valid argument to closure_ret_closure, since f: T and for this monomorphization, T = {integer}.
Even writing out the logic behind this error is quite confusing, so it would be helpful to improve these diagnostics in case a user accidentally stumbles upon them. I'm not entirely sure how to accomplish this, but making it clearer that the first diagnostic (E0277) is referring to the return type might help.
Unfortunately, I'm not sure if anything can be done regarding the rather strange T: FnOnce() -> T bound, since it's quite possible to implement a type that can satisfy T: FnOnce() -> T on nightly. This compiles and runs successfully (playground):
#![feature(fn_traits)]
#![feature(unboxed_closures)]
struct Foo;
impl FnOnce<()> for Foo {
type Output = Self;
extern "rust-call" fn call_once(self, _: ()) -> Self::Output {
self
}
}
fn closure_ret_closure<T: FnOnce() -> T>(f: T) -> T {
f()
}
fn main() {
closure_ret_closure(Foo);
}
Since (as far as I know) FnOnce can only be implemented on nightly, we may be able to get away with making trait bounds like T: FnOnce() -> T a deny-by-default lint, or at least warn-by-default. I'm not certain if this would be entirely feasible/possible with the current trait system, however, since that would be equivalent to linting against this:
trait Foo {
type Bar;
}
fn baz<T: Foo<Bar = T>>(_: T) { unimplemented!() }
Meta
rustc --version --verbose:
rustc 1.48.0 (7eac88abb 2020-11-16)
binary: rustc
commit-hash: 7eac88abb2e57e752f3302f02be5f3ce3d7adfb4
commit-date: 2020-11-16
host: x86_64-unknown-linux-gnu
release: 1.48.0
LLVM version: 11.0
rustc +nightly --version --verbose:
rustc 1.51.0-nightly (257becbfe 2020-12-27)
binary: rustc
commit-hash: 257becbfe4987d1f7b12af5a8dd5ed96697cd2e8
commit-date: 2020-12-27
host: x86_64-unknown-linux-gnu
release: 1.51.0-nightly
@rustbot modify labels: +A-diagnostics +A-traits +C-enhancement +D-confusing +T-compiler
I tried this code (playground):
I expected to see this happen:
Either a diagnostic proclaiming that the type parameter for
closure_ret_closureis recursive or a diagnostic that clearly states that|| 4does not returnFnOnce() -> FnOnce() -> FnOnce() -> ....Instead, this happened:
Two seemingly conflicting diagnostics are emitted:
The first diagnostic claims that
{integer}, the type of the value returned by the closure, is not aFnOnce() -> .... It is not immediately clear that this is in reference to the return type, so the user may believe the value in question is the parameter.The second diagnostic claims that it expects the parameter to be of type
{integer}. This is quite strange, because the previous diagnostic just told us thatT: FnOnce() -> FnOnce() -> ..., and{integer}does not fit that requirement.What may be happening is that since the result of calling
|| 4is of type{integer}, the compiler believesTmust be{integer}. Since{integer}does not implementFnOnce() -> {integer}, it doesn't fit the bounds onT, generating the first diagnostic. Then, the compiler sees that|| 4is not a valid argument toclosure_ret_closure, sincef: Tand for this monomorphization,T={integer}.Even writing out the logic behind this error is quite confusing, so it would be helpful to improve these diagnostics in case a user accidentally stumbles upon them. I'm not entirely sure how to accomplish this, but making it clearer that the first diagnostic (E0277) is referring to the return type might help.
Unfortunately, I'm not sure if anything can be done regarding the rather strange
T: FnOnce() -> Tbound, since it's quite possible to implement a type that can satisfyT: FnOnce() -> Ton nightly. This compiles and runs successfully (playground):Since (as far as I know)
FnOncecan only be implemented on nightly, we may be able to get away with making trait bounds likeT: FnOnce() -> Ta deny-by-default lint, or at least warn-by-default. I'm not certain if this would be entirely feasible/possible with the current trait system, however, since that would be equivalent to linting against this:Meta
rustc --version --verbose:rustc +nightly --version --verbose:@rustbot modify labels: +A-diagnostics +A-traits +C-enhancement +D-confusing +T-compiler