Specialization of a trait over `Fn`

I am trying to get the below code to specialize based on the type F which has to be a function or a closure (implements FnOnce). FnOnce is a super-trait of Fn, so I expect to be able to provide a general implementation of the trait Runnable for FnOnce and then specialize the implementation for Fn (to have a different behavior).

#![feature(unboxed_closures)] // to switch from parenthetical notation to generics for `Fn*` traits
#![feature(fn_traits)] // to use `call_once` and `call` methods on Fn* traits
#![feature(min_specialization)] // for specialization of trait implementations

struct Callable<
        A,  // arguments as a tuple
        R,  // return type
        F,  // Fn trait (like Fn, FnOnce, and FnMut)
    >
where 
    F: FnOnce<A, Output = R>,
{
}

pub trait Runnable {
    fn run(&mut self);
}

default impl<A, R, F> Runnable
    for Callable<
        A,  // arguments as a tuple
        R,  // return type
        F,  // Fn trait (like Fn, FnOnce, and FnMut)
    >
where
    F: FnOnce<A, Output = R>,
{
    fn run(self) {
        println!("Just testing the run method for FnOnce...");
    }
}

impl<A, R, F> Runnable
    for Callable<
        A,  // arguments as a tuple
        R,  // return type
        F,  // Fn trait (like Fn, FnOnce, and FnMut)
    >
where
    F: Fn<A, Output = R>,
{
    fn run(self) {
        println!("Just testing the run method for Fn...");
    }
}

pub fn main(){
    
}

Here is a playground link for reference: Rust Playground

I get errors about overflow. Could someone please point out what I am doing wrong here, and how I can accomplish this?

Don't put default on the impl, instead put it on the functions. I've fixed up your playground here. Note to specialize on traits you'll need to use specialization not min_specialization.

@RustyYato Thank you!

  • Do you happen to know why min_specialization does not work in this specific case?
  • Also, do you know what the overflow errors were about?

min_specialization only allows you to specialize on types and certain special traits (like Copy). This is to sidestep some very subtle lifetime unsoundness issues.

Specializing on lifetimes is unsound because the trait solver could disagree with codegen on which implementation to use (lifetimes are erased before codegen). Which has disastrous consequences. This issue can come up in some really subtle ways, such as

default impl<T, U> Trait for (T, U) { ... }

impl<T> Trait for (T, T) { ... }

This specialization requires that lifetimes on each part of the tuple to be identical. Detecting this is intractable, because this sort of constraint may come from another trait.

default impl<T> Trait for T { ... }
impl<T: Trait2> Trait for T { ... }

impl<T> Trait2 for (T, T) { ... }

But by banning specialization on traits, we can resolve the issue for now, and just check for lifetime specialization.

I'm not sure about the overflow errors. I think it's because work on default impl is incomplete.

5 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.