rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
99.14k stars 12.8k forks source link

type parameter not constrained when using closure bounds #25041

Open BurntSushi opened 9 years ago

BurntSushi commented 9 years ago

This works fine:

trait Foo {}
impl<F, A> Foo for F where F: Fn() -> A {}

But this:

trait Foo {}
impl<F, A> Foo for F where F: Fn(A) {}

produces a compile error:

<anon>:2:9: 2:10 error: the type parameter `A` is not constrained by the impl trait, self type, or predicates [E0207]
<anon>:2 impl<F, A> Foo for F where F: Fn(A) {}

I'm not sure if this is intended behavior or not, but I definitely don't understand why the former is accepted and the latter is not.

Version:

[andrew@Liger quickcheck] rustc --version
rustc 1.1.0-nightly (c4b23aec4 2015-04-29) (built 2015-04-29)
BurntSushi commented 9 years ago

Another data point:

trait Foo {}
impl<F, A, T> Foo for F where F: Fn(A) -> T {}

produces

<anon>:2:9: 2:10 error: the type parameter `A` is not constrained by the impl trait, self type, or predicates [E0207]
<anon>:2 impl<F, A, T> Foo for F where F: Fn(A) -> T {}
                 ^
<anon>:2:12: 2:13 error: the type parameter `T` is not constrained by the impl trait, self type, or predicates [E0207]
<anon>:2 impl<F, A, T> Foo for F where F: Fn(A) -> T {}

Which says both types are not constrained. Weird.

tomjakubowski commented 9 years ago

a slightly reduced test case demonstrating that it's not the Fn family of traits in particular:

https://play.rust-lang.org/?gist=93a594257a2c08bd33c3&version=nightly

trait MyFn<In> {
    type Out;
}

trait Foo {}

impl<F, A> Foo for F where F: MyFn<A, Out=()> {}

fn main() {}
<anon>:7:9: 7:10 error: the type parameter `A` is not constrained by the impl trait, self type, or predicates [E0207]
<anon>:7 impl<F, A> Foo for F where F: MyFn<A, Out=()> {}
                 ^
<anon>:7:9: 7:10 help: see the detailed explanation for E0207
error: aborting due to previous error
playpen: application terminated with error code 101
arielb1 commented 9 years ago

This is completely intentional - see rust-lang/rfcs#447. You can (given #![feature(unboxed_closures)]) manually implement Fn with multiple distinct sets of input parameters:

#![feature(unboxed_closures,core)]
use std::mem;

trait Foo {
    fn foo(&self) -> usize;
}
impl<F, A> Foo for F where F: FnOnce(A) {
    fn foo(&self) -> usize { mem::size_of::<A>() }
}

struct S;
impl FnOnce<(u32,)> for S {
    type Output = ();
    extern "rust-call" fn call_once(self, _args: (u32,)) {}
}
impl FnOnce<(u8,)> for S {
    type Output = ();
    extern "rust-call" fn call_once(self, _args: (u8,)) {}
}
fn main() {
    println!("{}", <S as Foo>::foo(&S)); // which impl is used?
}

The "correct" fix is probably to add a type parameter to foo.

alevy commented 9 years ago

There seems to be a related issue in which the checker is overly conservative:

trait Foo {}

struct Bar<T> { _b: T }

impl<E, T: Unsize<E>> Foo for Bar<T> {}

does not compile, even though E can be determined uniquely.

arielb1 commented 9 years ago

@alevy

Unsize is a trait with a type parameter, not an associated type. I don't remember why @eddyb did it that way, but this is how it is. [u8; 1] implements both Unsize<[u8]> and Unsize<fmt::Debug>.

eddyb commented 9 years ago

Each type can implement many traits and T: Trait implies T: Unsize<Trait> (if the trait is object-safe).

alevy commented 9 years ago

But if you know T, you also know E. Bar<T> means that you know T, so E is never ambiguous in the impl.

eddyb commented 9 years ago

@alevy That's just plain wrong, there are many possible substitutions for E, @arielb1 even gave an example.... Unless you meant to write Unsize<[E]>.

alevy commented 9 years ago

@eddyb I see. you're right. I actually did mean Unsize<[E]>, but I don't understand why that makes a difference. Will try to ask on IRC instead of spamming this issue though.

shepmaster commented 8 years ago

Is the error message misleading, or am I missing a nuance?

trait Foo {}

impl<F, A> Foo for F
    where F: Fn(A),
          A: Foo,
{}

Has the error

the type parameter A is not constrained by the impl trait, self type, or predicates

But, I do have a constraint on A in the predicates — A: Foo. Which part of "constraint" or "predicate" am I misreading?

eddyb commented 8 years ago

@shepmaster it's definitely misleading - effectively, what it's trying to tell you is that it cannot get A back from either the implemented trait (Foo) or the type implemented on (F). F: Fn(A) is not enough to extract A from F because one F type can have multiple Fn impls with various arguments (although this wouldn't happen with closures).

bfops commented 8 years ago

I'm hitting this issue too. Is it true for all functions and closures that A is uniquely specified by the type of F? It would be nice to have a trait that enforces that, since most of the types that implement Fn/FnMut/FnOnce have a specified input and return type.

trait ActuallyAFn {
  type Param;
  type Return;
  fn call(&self, param: Self::Param) -> Return;
}
J-F-Liu commented 7 years ago

I also meet this issue while writing the following code:

use std::ops::Add;

pub struct Input<'a, I: 'a> {
  pub data: &'a [I],
  pub position: usize,
}

impl<'a, I: 'a> Input<'a, I> {
  pub fn new(input: &'a [I]) -> Input<I> {
    Input {
      data: input,
      position: 0,
    }
  }

  pub fn current(&self) -> Option<I>
    where I: Copy + Clone + 'static
  {
    if self.position < self.data.len() {
      Some(self.data[self.position])
    } else {
      None
    }
  }

  pub fn advance(&mut self) {
    self.position += 1;
  }
}

#[derive(Debug, PartialEq)]
pub enum Error {
  Incomplete,
  Mismatch { message: String, position: usize },
}

pub type Result<O> = ::std::result::Result<O, Error>;

pub struct Parser<I, O> {
  method: Box<Fn(&mut Input<I>) -> Result<O>>,
}

impl<I, O> Parser<I, O> {
  pub fn new<P>(parse: P) -> Parser<I, O>
    where P: Fn(&mut Input<I>) -> Result<O> + 'static
  {
    Parser { method: Box::new(parse) }
  }

  pub fn parse(&self, input: &mut Input<I>) -> Result<O> {
    (self.method)(input)
  }
}

impl<I, O1, O2> Add for Parser<I, O1> {
  type Output = Parser<I, (O1, O2)>;

  fn add(self, other: Parser<I, O2>) -> Self::Output
    where I: 'static,
          O1: 'static,
          O2: 'static
  {
    Parser::new(move |input: &mut Input<I>| {
      self.parse(input).and_then(|out1| other.parse(input).map(|out2| (out1, out2)))
    })
  }
}

The error message is:

error[E0207]: the type parameter `O2` is not constrained by the impl trait, self type, or predicates
  --> src/parser.rs:81:13
   |
81 | impl<I, O1, O2> Add for Parser<I, O1> {
   |             ^^ unconstrained type parameter
eddyb commented 7 years ago

@J-F-Liu Completely intentional, associated types are supposed to be determined from Self and trait parameters alone but yours is extra generic.

EDIT: on a third read I noticed the O2 in the add argument. If the add signature correct then you forgot to specify the optional RHS type, i.e. the impl should not be for Add but Add<Parser<I, O2>>.

J-F-Liu commented 7 years ago

@eddby ah, thanks, the following code works:

impl<I, O, U> Add<Parser<I, U>> for Parser<I, O> {
    type Output = Parser<I, (O, U)>;

    fn add(self, other: Parser<I, U>) -> Self::Output
        where I: 'static,
              O: 'static,
              U: 'static
    {
        Parser::new(move |input: &mut Input<I>| {
            self.parse(input).and_then(|out1| other.parse(input).map(|out2| (out1, out2)))
        })
    }
}

The above code is critical for my parser combinator library.

J-F-Liu commented 7 years ago

While writing pom 2.0 meet this error again:

pub enum Error {
  Incomplete,
  Mismatch { message: String, position: usize },
  Conversion { message: String, position: usize },
  Custom { message: String, position: usize, inner: Option<Box<Error>> },
}
pub type Result<O> = ::std::result::Result<O, Error>;
pub trait Parser<'a, I, O> {
  fn parse(&self, input: &'a [I], start: usize) -> Result<(O, usize)>;
}

pub struct Left<P1, P2>(P1, P2);
impl<'a, I, O1, O2, P1: Parser<'a, I, O1>, P2: Parser<'a, I, O2>> Parser<'a, I, O1> for Left<P1, P2> {
  fn parse(&self, input: &'a [I], start: usize) -> Result<(O1, usize)> {
    self.0.parse(input, start).and_then(|(out1, pos1)|
      self.1.parse(input, pos1).map(|(_, pos2)|
        (out1, pos2)
      )
    )
  }
}
13 | impl<'a, I, O1, O2, P1: Parser<'a, I, O1>, P2: Parser<'a, I, O2>> Parser<'a, I, O1> for Left<P1, P2> {
   |                 ^^ unconstrained type parameter

Can not be solved this time. I tried to add PhantomData, but transfer the error to the following code:

impl<P, Q, O> Sub<Combinator<Q>> for Combinator<P>
{
  type Output = Combinator<Left<P,Q,O>>;

  fn sub(self, other: Combinator<Q>) -> Self::Output {
    Combinator(Left(self.0, other.0, PhantomData))
  }
}
429 | impl<P, Q, O> Sub<Combinator<Q>> for Combinator<P>
    |            ^ unconstrained type parameter
rkarp commented 7 years ago

Interestingly, this works:

use std::marker::PhantomData;

trait Foo {}

struct Function<F, I, O> {
    in_p: PhantomData<I>,
    out_p: PhantomData<O>,
    function: F
}

impl<F, I, O> Foo for Function<F, I, O>
    where F: Fn(I) -> O
{}

But this doesn't:

use std::marker::PhantomData;

trait Foo {}

struct Function<F> {
    function: F
}

impl<F, I, O> Foo for Function<F>
    where F: Fn(I) -> O
{}
eddyb commented 7 years ago

@rkap That is a direct consequence of our rules. Do note that you don't need O in a PhantomData because it's fully determined by F and I.

maxammann commented 2 years ago

I tried to implement a pipeline with steps. Steps implement the Processable trait. I failed to implement that trait directly for Fn(I, Context) -> O.

I suppose this is not possible, right now, correct?


pub struct PipelineContext {
}

impl PipelineContext {
    pub fn teardown(self) -> Box<dyn PipelineProcessor> {
        self.processor
    }
}

pub trait Processable {
    type Input;
    type Output;

    fn process(&self, input: Self::Input, context: &mut PipelineContext) -> Self::Output;
}

pub struct PipelineStep<P, N>
where
    P: Processable,
    N: Processable<Input = P::Output>,
{
    process: P,
    next: N,
}

impl<P, N> PipelineStep<P, N>
where
    P: Processable,
    N: Processable<Input = P::Output>,
{
    pub fn new(process: P, next: N) -> Self {
        Self { process, next }
    }
}

impl<P, N> Processable for PipelineStep<P, N>
where
    P: Processable,
    N: Processable<Input = P::Output>,
{
    type Input = P::Input;
    type Output = N::Output;

    fn process(&self, input: Self::Input, context: &mut PipelineContext) -> Self::Output {
        let output = self.process.process(input, context);
        self.next.process(output, context)
    }
}

pub struct EndStep<I> {
    phantom: PhantomData<I>,
}

impl<I> Default for EndStep<I> {
    fn default() -> Self {
        Self {
            phantom: PhantomData::default(),
        }
    }
}

impl<I> Processable for EndStep<I> {
    type Input = I;
    type Output = I;

    fn process(&self, input: Self::Input, _context: &mut PipelineContext) -> Self::Output {
        input
    }
}

impl<I, O> Processable for &fn(input: I, context: &mut PipelineContext) -> O {
    type Input = I;
    type Output = O;

    fn process(&self, input: Self::Input, context: &mut PipelineContext) -> Self::Output {
        (self)(input, context)
    }
}

impl<I, O> Processable for fn(input: I, context: &mut PipelineContext) -> O {
    type Input = I;
    type Output = O;

    fn process(&self, input: Self::Input, context: &mut PipelineContext) -> Self::Output {
        (self)(input, context)
    }
}

pub struct ClosureProcessable<F, I, O>
where
    F: Fn(I, &mut PipelineContext) -> O,
{
    func: F,
    phantom_i: PhantomData<I>,
}

impl<F, I, O> Processable for ClosureProcessable<F, I, O>
where
    F: Fn(I, &mut PipelineContext) -> O,
{
    type Input = I;
    type Output = O;

    fn process(&self, input: Self::Input, context: &mut PipelineContext) -> Self::Output {
        (self.func)(input, context)
    }
}

#[cfg(test)]
mod tests {
    use crate::io::pipeline::{
        ClosureProcessable, EndStep, PipelineContext, PipelineProcessor, PipelineStep, Processable,
    };
    use std::sync::mpsc;

    pub struct DummyPipelineProcessor;

    impl PipelineProcessor for DummyPipelineProcessor {}

    fn add_one(input: u32, context: &mut PipelineContext) -> u8 {
        input as u8 + 1
    }

    fn add_two(input: u8, context: &mut PipelineContext) -> u32 {
        input as u32 + 2
    }

    #[test]
    fn test() {
        let mut context = PipelineContext {
        };
        let output: u32 = PipelineStep {
            process: add_two as fn(u8, &mut PipelineContext) -> u32,
            next: EndStep::default(),
        }
        .process(5u8, &mut context);

        assert_eq!(output, 7);

        let output = PipelineStep {
            process: add_one as fn(u32, &mut PipelineContext) -> u8,
            next: PipelineStep {
                process: add_two as fn(u8, &mut PipelineContext) -> u32,
                next: EndStep::default(),
            },
        }
        .process(5u32, &mut context);

        assert_eq!(output, 8);

        let output: u32 = PipelineStep {
            process: ClosureProcessable {
                func: |input: u8, context| -> u32 {
                    return input as u32 + 2;
                },
                phantom_i: Default::default(),
            },
            next: EndStep::default(),
        }
        .process(5u8, &mut context);

        assert_eq!(output, 7);
    }
}
eddyb commented 2 years ago

@maxammann It's not just "right now", the definition of that trait is incompatible with Rust functions.

That is, this trait definition:

pub trait Processable {
    type Input;
    type Output;

    fn process(&self, input: Self::Input, context: &mut PipelineContext) -> Self::Output;
}

Suggests that every implementer of Processable can only ever have one input and one output type.

It's suitable for () -> (Input, Output) not Input -> Output.


I didn't check if everything else is agreeable with this version, but it's the arguably correct one:

pub trait Processable<I> {
    type Output;

    fn process(&self, input: I, context: &mut PipelineContext) -> Self::Output;
}
maxammann commented 2 years ago

I didn't check if everything else is agreeable with this version, but it's the arguably correct one:

pub trait Processable<I> {
    type Output;

    fn process(&self, input: I, context: &mut PipelineContext) -> Self::Output;
}

So, my definition of Processable works nicely for my usecase even though it is incompstible with functions.

I already tried for implement the quoted version of Processable for Fn(I) -> O. I failed because of the same reasons. The problem was that I was unable to forward the generic I from Processable to the Fn.