Alternatives when trying to use impl syntax in trait implementation

I'm trying to use a closure in a struct which makes it possible to implement a bunch of useful traits like From.

In this case, I'd probably just end up using boxed closures, but is there some type magic I can do that would get it to work without using Box?

struct Lazy<T, F: FnOnce() -> T> {
    func: F,
}

impl<T, F: FnOnce() -> T> Lazy<T, F> {
    fn evaluate(self) -> T {
       (self.func)() 
    }
}

// This doesn't work :(
impl<T, F: FnOnce() -> T> From<T> for Lazy<T, F> {
    fn from(value: T) -> Lazy<T, impl FnOnce() -> T> {
        Lazy {
            func: || value
        }
    }
}

// This works! But the ergonomics aren't as good as using
// Lazy::from or value.into()
fn from_value<T>(value: T) -> Lazy<T, impl FnOnce() -> T> {
    Lazy {
        func: || value
    }
}

fn main() {
    let value = from_value(10);
    println!("Value is {}", value.evaluate());

    // let value = Lazy::from(10);
    // println!("Value is {}", value.evaluate());
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0562]: `impl Trait` not allowed outside of function and inherent method return types
  --> src/main.rs:13:34
   |
13 |     fn from(value: T) -> Lazy<T, impl FnOnce() -> T> {
   |                                  ^^^^^^^^^^^^^^^^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0562`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

With type_alias_impl_trait it will be possible:

#![feature(type_alias_impl_trait)]
struct Lazy<T, F: FnOnce() -> T> {
    func: F,
}

impl<T, F: FnOnce() -> T> Lazy<T, F> {
    fn evaluate(self) -> T {
       (self.func)() 
    }
}


type ClosureInImplFromForLazy<T> = impl FnOnce() -> T;
impl<T> From<T> for Lazy<T, ClosureInImplFromForLazy<T>> {
    fn from(value: T) -> Lazy<T, ClosureInImplFromForLazy<T>> {
        Lazy {
            func: || value
        }
    }
}


fn main() {

    let value = Lazy::from(10);
    println!("Value is {}", value.evaluate());
}
2 Likes

In the mean time you may consider this kind of approach if this fits the remainder of your API.

once_cell's Lazy<T, F> chose to default the F parameter to fn() -> T which lacks the inlining optimization opportunity of a generic argument but allows it to be used at the top level on stable.

1 Like

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.