Consider the following situation:
use ::core::ops::Add;
trait CurryAdd : Add + Sized {
type Adder : FnOnce(Self) -> Self::Output;
fn curry_add (lhs: Self) -> Self::Adder;
}
that is, the trait requires an explicitely named type implementing one of the Fn
traits. Although existential types are trying to come up with a general solution for this very particular kind of case, I still think that it is a pity it is not possible to implement Fn
traits on custom types.
Teaching-wise, it would be great to be able to show the closures-are-objects “equivalence”, while also allowing to show the unique case of Rust with three callable interfaces.
I understand that the precise internals of Fn
traits must remain unstable at the moment, and when we think about it, it is plausible that they remain unstable forever (call operator and closures need to be able to be constantly improved).
Idea
But, since the current interface is already quite acceptable (I think most users would not complain), it could be nice to offer the current interface as a frozen stable “alternative”. This can be achieved with two things:
-
a stable version of the current
fn_traits
://! names could obviously be different pub trait CallableByValue<Args> { type Output; fn call_by_value (self, _: Args) -> Self::Output; } pub trait CallableByMut<Args> : CallableByValue<Args> { fn call_by_mut (&'_ mut self, _: Args) -> Self::Output; } pub trait CallableByRef<Args> : CallableByMut<Args> { fn call_by_ref (&'_ self, _: Args) -> Self::Output; }
-
a wrapper that implements the unstable
fn_traits
when the wrappee implements the stable ones:#[repr(transparent)] struct FnMarker<F>(pub F); impl<Args, F> FnOnce<Args> for FnMarker<F> where F : CallableByValue<Args>, { type Output = F::Output; #[inline] extern "rust-call" fn call_once (self: Self, args: Args) -> Self::Output { self.0.call_by_value(args) } } impl<Args, F> FnMut<Args> for FnMarker<F> where F : CallableByMut<Args>, { #[inline] extern "rust-call" fn call_mut (self: &'_ mut Self, args: Args) -> Self::Output { self.0.call_by_mut(args) } } impl<Args, F> Fn<Args> for FnMarker<F> where F : CallableByRef<Args>, { #[inline] extern "rust-call" fn call (self: &'_ Self, args: Args) -> Self::Output { self.0.call_by_ref(args) } }
- this would be the only part that would need to stay in sync with
fn_traits
as they evolve.
- this would be the only part that would need to stay in sync with
Usage
Basic
use ::core::ops::Add;
trait CurryAdd : Add + Sized {
type Adder : FnOnce(Self) -> Self::Output;
fn curry_add (lhs: Self) -> Self::Adder;
}
struct CurryAdder<T> (T);
impl<T : Add> callable::CallableByValue<(T,)> for CurryAdder<T> {
type Output = T::Output;
fn call_by_value (self, (rhs,): (T,)) -> Self::Output
{
self.0 + rhs
}
}
impl<T : Add> CurryAdd for T {
type Adder = callable::FnMarker<CurryAdder<T>>;
fn curry_add (lhs: Self) -> Self::Adder
{
callable::FnMarker(CurryAdder(lhs))
}
}
fn main ()
{
dbg!(
u8::curry_add(42)(27)
);
dbg!(
i32::curry_add(42)(27)
);
}
With some sugar (waiting for specialization)
use ::core::ops::Add;
trait CurryAdd : Add + Copy + Sized {
type Adder : Fn(Self) -> Self::Output;
fn curry_add (lhs: Self) -> Self::Adder;
}
struct CurryAdder<T> (T);
impl_Callable! {
for [T : Add + Copy + Sized]
|self: &CurryAdder<T>, (rhs,) : (T,)| -> T::Output,
{
self.0 + rhs
}
}
impl<T : Add + Copy + Sized> CurryAdd for T {
type Adder = callable::FnMarker<CurryAdder<T>>;
fn curry_add (lhs: Self) -> Self::Adder
{
callable::FnMarker(CurryAdder(lhs))
}
}
Alternatives
-
Existential types look like the best candidate, but it would not allow defining our own object-and-also-closures (e.g., inspecting the captured environment of a closure, or mutating it).
-
If we were to be able to write, for instance,
(&x).eq
to mean|y| x.eq(y)
, then a new kind of type could be defined to express the type of such a closure:(&'a T).eq
(orx.eq : T.eq
).For instance,
-
(&'a T).eq : 'a + Fn(&T) -> bool
(closure|y: &T| x.eq(y)
) -
T.eq : 't + Fn(&T) -> bool where T : 't
(closuremove |y: &T| x.eq(y)
)
-
This would avoid needing FnMarker<F>
, since we would be able to use F.call_by_xxx
. (for instance, CurryAdder<T>.call_by_value
for the FnOnce
example, or CurryAdder<T>.call_by_ref
for the Fn : FnMut : FnOnce
example)