#Is it possible to put trait bounds on the associated type for a FnOnce?

9 messages · Page 1 of 1 (latest)

fossil sage
#

Something conceptually like this:

pub struct S<F> {
f: F
}

impl<F, I, O> Iterator for S<F>
where
F: FnOnce(I),
<F as FnOnce(I,)>::Output: IntoIterator<Item=O>
{
type Item = O;
fn next(&mut self) -> OptionSelf::Item {
unimplemented!()
}
}
https://doc.rust-lang.org/std/ops/trait.FnOnce.html

FnOnce is just a trait, and its associated type is "Output". The Args are just type parameters. I'm struggling to put bounds on this FnOnce without getting error messages though, can someone show me the right way to put trait bounds on the associated type for a FnOnce?

urban hedge
#

You can introduce some more generic parameters to describe I, O, and the return type of the function, R

Then can place bounds like this:

    where
        F: FnOnce(I) -> R,
        R: IntoIterator<Item = O>

The downside is that all the generics introduced must be used in either the trait being implemented, or the type it's being implemented on. To prevent the same trait being implemented multiple times on the same concrete type.

So that means S would have to be generic over the new parameters unfortunately. This works, but we have to use PhantomData to keep the compiler happy about the new generics not being used

#

?play

use core::marker::PhantomData;

pub struct S<I, O, R, F> {
    _i: PhantomData<fn(I)>,
    _o: PhantomData<fn()->O>,
    _r: PhantomData<fn()->R>,
    _f: F
}

impl<F, I, O, R> Iterator for S<I, O, R, F>
    where
        F: FnOnce(I) -> R,
        R: IntoIterator<Item = O>
{
    type Item = O;
    fn next(&mut self) -> Option<Self::Item> {
        unimplemented!()
    }
}

impl<F, I, O, R> From<F> for S<I, O, R, F> 
where
    F: FnOnce(I) -> R,
    R: IntoIterator<Item = O>
{
    fn from(f: F) -> Self {
        Self {
            _i: PhantomData,
            _o: PhantomData,
            _r: PhantomData,
            _f: f
        }
    }
}

fn some_func(_i: i32) -> impl IntoIterator<Item = u32> {
    std::iter::empty()
}

fn main() {
    let mut s: S<_, _, _, _> = some_func.into();
    s.next().unwrap();
}
sleek fableBOT
#
     Running `target/debug/playground`
thread 'main' panicked at 'not implemented', src/main.rs:17:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
urban hedge
#

The other approach could be to bind the generics for the function input/output to the trait itself but that requires a trait that has those generic parameters on it - Iterator doesn't have any so they have to be bound to the type instead when implementing

So the other approach could be to make a new trait "FunctionIterator" or something that does have support for the input/output generics

low fable
#

?play

pub struct S<F, I> {
    f: F,
    _i: std::marker::PhantomData<I>,
}

impl<F, I, I2, O> Iterator for S<F, I>
where
    F: FnOnce(I) -> I2,
    I2: IntoIterator<Item = O>,
{
    type Item = O;
    fn next(&mut self) -> Option<Self::Item> {
        unimplemented!()
    }
}
sleek fableBOT
#
     Running `target/debug/playground`
low fable
#

the only way you are allowed to use FnOnce is with the FnOnce(Arg) -> Ret syntax, which makes the result act like a type parameter even though it is an associated type

urban hedge
#

It took me a while to understand why I2 and O don't need to be constrained to the type there since I knew that F could implement multiple FnOnce traits with different input types....I had thought the compiler would complain about I2 being unconstrained for the same reason.

However I2 is an associated type on FnOnce trait so it can't have different returns for the same FnOnce(I). I got thrown by the syntax sugar for the FnOnce trait and thought I2 was a generic parameter of FnOnce 😄

I'll remember that for the future