- Feature Name: functional_closure
- Start Date: (fill me in with today's date, YYYY-MM-DD)
- RFC PR:
- Rust Issue:
Summary
A use of Rust's closure syntax creates a unique, unnamed type implementing the
special Fn, FnMut, or FnOnce traits. This RFC proposes extending that
behavior to an implementation of any "functional" trait - one that has a single
non-defaulted, non-static method. This is very similar to the behavior of Java
8's closures.
Motivation
There are contexts in which you want a specific trait rather than the more
generic Fn* traits. For example, the trait may provide defaulted methods in
addition to the "main" method. Take the Iterator trait. It has a single
required method, next, with a great deal of default methods added on top.
Imagine you have a Foo::bar method which returns an Option<u32>, and you
want to pass that into a function expecting an Iterator<Item = u32>. Currently
you have to manually manually create a wrapper struct:
struct FooIterator(Foo);
impl Iterator for FooIterator {
type Item = u32;
fn next(&mut self) -> Option<u32> {
self.0.bar()
}
}
let foo = ...;
some_function(FooIterator(foo));However, this could be simplified to just
let mut foo = ...;
some_function(|| foo.bar());A somewhat common convention is to add a blanket implementation of a trait for
types implementing the relevant closure type. In the Iterator case, this would
hypthetically look like:
impl<T, R> Iterator for T
where
T: FnMut() -> Option<R>
{
type Item = R;
fn next(&mut self) -> Option<R> {
self()
}
}However, there are limitations to this approach. It has to be explicitly
implemented for each trait - this was not done for Iterator, for example.
It also breaks backwards compatibility to add after the fact since downstream
concrete implementations for types which also implement FnMut() -> Option<R>
will suddenly conflict.
In addition, blanket implementations like this don't compose well with other
implementations you also want. For example, Iterator is implemented for
mutable references to iterators:
impl<'a, T> Iterator for &'a mut T
where
T: ?Sized + Iterator
{
type Item = T::Item;
fn next(&mut self) -> Option<T::Item> {
(**self).next()
}
}This implementation conflicts with the proposed implementation for closures.
In other contexts, you have a trait with only one method, but common
implementations are complex enough that simply using a Fn* trait is not
appropriate. For example, consider the Iron
Handler
trait. It is responsible for processing an HTTP request and producing the
response. It has a blanket implementation for all Fn implementations with a
matching signature, which allows for very convenient "toy" server
implementations:
fn main() {
Iron::new(|_: &mut Request| {
Ok(Response::with((status::Ok, "Hello World!")))
}).http("localhost:3000").unwrap();
}However, concrete types also implement Handler. Take for example the
Router type, which
selects a sub-Handler based on the request's endpoint.
There are also contexts where that workaround will not work at all. Consider,
for example, Serde's DeserializeSeed trait:
pub trait DeserializeSeed<'de>: Sized {
type Value;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>;
}We can't play the same trick here - note that the deserialize method itself is
parameterized. We would need to require that T implements FnOnce(D) for
all D: Deserializer, this requires Higher Ranked Trait Bounds (HRTB) syntax
which isn't supported in Rust's type system (this is supported for lifetimes
with the where T: for <'a> FnOnce(&'a D) syntax).
Detailed design
A trait is functional if it:
- Has a single method without a default implementation,
- that method is not static,
- and has no supertraits which are not auto traits.
For example, Iterator and DeserializeSeed<'de> above are both functional.
Examples of traits which are not functional:
// Method is static.
pub trait Default {
fn default() -> Self;
}
// Two methods which need implementation.
pub trait Bar {
fn method_a(&self);
fn method_b(&self);
}
// No method which needs an implementation.
pub trait Baz {
fn defaulted(&self) {}
}
// Has a non-auto supertrait
pub trait Buz: Eq {
fn buz(&self);
}Note that, strangely, Fn and FnMut as they exist today are not functional
by this definition! Fn and FnMut have FnMut and FnOnce as supertraits
respectively. We will treat them as a special case. This is somewhat
unfortunate - we may also be able to adjust the definitions of the Fn* traits
and replace the supertrait bounds with blanket implementations. We have
some flexibility here because the definitions of the Fn* traits are not
stable.
If the type of a closure literal is inferred to be a type implementing a functional trait, the compiler will generate an appropriate implementation of that trait.
This implementation generation will also involve the selection of associated
types. This is already handled today, as the return type of Fn* traits is an
associated type. In theory, there may exist associated types which are not
involved in the signature of the functional method. In this case, those
associated types may need to be constrained externally:
trait Foo {
// Can be inferred from the closure.
type Bar;
// Requires separate hinting.
type Baz;
fn foo(&self) -> Self::Bar;
}
fn use_a_foo<F>(foo: F)
where
F: Foo<Baz = u32>
{
// ...
}
// valid, as `Baz`'s type is selected by `use_a_foo`'s signature.
use_a_foo(|| 0i32);Like integer type inference has an implicit fallback to i32 if the exact type
can't otherwise be inferred, closure literals will have an implicit fallback to
the Fn* traits. This is required since currently valid code like this would
fail to compile otherwise:
fn foo() {
let _ = || ();
}How We Teach This
The term functional is borrowed from Java 8's similar feature. However, "functional programming" is already a separate, well defined term, so this may not be the best word choice.
We will need to reframe the way we talk about the Fn* traits and how they
interact with closures. Currently, closures are just a convenient way of
generating implementations of the Fn* traits, but with this change, they'll
be convenient ways of generating implementation of any functional trait.
The Fn* traits are still valuable and will continue to be heavily used in
canonical Rust. There are many cases where all you want is "some function with
this signature", and the Fn* traits are the right way to express that. In
particular, as the only variadic traits in the language, they allow us to avoid
a profusion of Function, Function2, Function3, etc traits, as Java is
forced to deal with. The design decision of when to use a Fn* trait vs a
custom trait will not change all that much from how it is now. Usage of custom
traits may become a bit more common, but, we would, for example, still use
Fn* traits for methods like Iterator::position if we were redesigning the
Iterator trait with this feature in mind.
The Iterator trait can make a good example of usage when teaching about this
feature:
fn print_items<I>(mut it: I)
where
I: Iterator
I::Item: Display
{
for item in it {
println!("{}", item);
}
}
// an iterator with nothing in it
print_items(|| None::<i32>);
// an iterator which counts from 1 to 5
let mut n = 0;
print_items(|| {
if n == 5 {
None
} else {
n += 1;
Some(n)
}
});Drawbacks
If closure syntax can implement any functional trait, it can become more difficult to visually understand the types involved in a piece of code.
There is a risk that existing code fails to types infer due to this change.
Alternatives
Traits could have to opt-in to being functional. It's not clear what this really buys much, however - any change that would cause a functional trait to become non-functional would be a breaking change regardless.
RFC 1650 proposed an extension of
the closure system to allow for generic implementations of the Fn* traits.
This enables some of the same use cases this RFC targets. However, it would
require HRTB before functions could declare such bounds. It was postponed
pending a revamp of the trait system.
Unresolved questions
How can we apply type hints to closures? One possibility is impl SomeTrait:
let thing: impl SomeTrait = |x| bar(x, 0);How intelligent will the inference of the type to implement be? For example,
functions which consume iterators tend to actually take an IntoIterator
argument. One might expect this code to compile:
fn foo<I>(it: I)
where
I: IntoIterator
{
// ....
}
foo(|| None::<i32>);There is a blanket implementation of IntoIterator for all types implementing
Iterator. The closure provided has a signature appropriate for
Iterator::next but not IntoIterator::into_iter. Would this be a type error
and have to be resolved via a type hint?
let it: impl Iterator = || None::<i32>;
foo(it);Concerns have been raised about the compiler closure infrastructure's ability to properly implement traits where the method is itself generic. In the short term, it may be necessary to additionally require that a trait's method be non-generic in order for it to be functional. Future advances to the compiler's implementation will allow that restriction to be droppoed in a backwards compatible manner.
Is the prohibition of static methods correct? There's no reason a closure
coultn't generate an implementation of e.g. Default. It's a bit strange for a
closure to be unable to close over any variables, but it would work. This
limitation can be relaxed in the future in a backwards compatible manner.