Imagine you have a function like this
fn foo(a: impl Into<u32>) -> u32 {
a.into()
}
Wouldn’t it be nice if you could also pass in a closure that takes no arguments and produces the expected type?
foo(|| 10);
Discuss.
Imagine you have a function like this
fn foo(a: impl Into<u32>) -> u32 {
a.into()
}
Wouldn’t it be nice if you could also pass in a closure that takes no arguments and produces the expected type?
foo(|| 10);
Discuss.
You can't impl<F: Fn()->T> Into<T> for F
because it could conflict with the blanket impl - unavoidably so, because a function object could hypothetically return an instance of the same type.
I see two problems:
You can already implementations like this for specific types,
struct Foo;
impl<F> From<F> for Foo
where
F: FnOnce() -> Foo,
{
fn from(f: F) -> Self {
f()
}
}
those implementations could have different behavior and would be in conflict with adding a generic implementation in the standard library
It’s possible (although not on stable Rust AFAICT) to have a type Foo
implement FnOnce() -> Foo
itself, so there’s the potential for conflict with the generic T: Into<T>
/ T: From<T>
implementation in the standard library
#![feature(unboxed_closures, fn_traits)]
struct Foo;
impl FnOnce<()> for Foo {
type Output = Foo;
extern "rust-call" fn call_once(self, _args: ()) -> Self::Output {
Foo
}
}
Which blanket implementation?
To your first point, it’s not the same.
To your second point, there’s always the name resolution syntax.
The one steffahn mentioned as the second point. It's a conflicting impl, where the two impls would implement the same trait for the same concrete type, which is a hard error in rust - Universal Function Call Syntax would not be sufficient to disambiguate between them.
I think the coherence and conflict problems could be avoided if the compiler generated an impl Into
for each individual closure type, rather than a generic impl for all Fn() -> T
types.
I also think that the problems are not insurmountable. The question is if people think it’s a good idea.
Well that's an interesting thought. It seems like it would conflict with (currently legal) impls like the one steffahn posted, though.
Users can't implement traits for anonymous closure types, so there's no danger of conflicting impls. Foo
in the example above is not a “closure type” (in the sense of “type of a closure literal”).
No. The documentation for From
says the following:
Note: This trait must not fail.
Here the conversion can fail because the function can panic.
But Foo implements From<F>
, causing any closure type that returns Foo to implement Into<Foo>
due to the blanket impl of Into. Surely that would conflict with an explicit impl of Into<Foo>
for such a closure?
I interpret fail as return an error not panic.
Oh, you're right. I misunderstood the source of the conflict.
Can’t you not add it to those that already have an implementation?
Leaving aside coherence issues, this impl seems potentially problematic in generic contexts. I think this would be an unexpected impl of into()
, and I think it's not unreasonable to instead require the caller to invoke the closure and pass the result.
I don't see what benefit this provides, since most of the time .into()
gets called near the top of the function anyway, so the same code would run in the same order.
It could help unify APIs like Option::unwrap_or
and Option::unwrap_or_else
, since T: Into<T>
.
That could be accomplished via a trait if it were desired — it doesn't have to be From
/Into
Wouldn't any trait for that job end up having the same problem as this one? Because a custom function object can be both a T
and a FnOnce()->T
?
What's wrong with the status quo? I'd actually think that the semantic distinction between just a default value vs. lazily evaluating an entire path of control flow matters. Nobody in reality writes code like .unwrap_or_else(|| 42)
if they can just write .unwrap_or(42)
.