Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

type parameter not constrained when using closure bounds #25041

Open
BurntSushi opened this issue May 2, 2015 · 21 comments
Open

type parameter not constrained when using closure bounds #25041

BurntSushi opened this issue May 2, 2015 · 21 comments
Labels
A-diagnostics Area: Messages for errors, warnings, and lints C-enhancement Category: An issue proposing an enhancement or a PR with one. D-confusing Diagnostics: Confusing error or lint that should be reworked. D-newcomer-roadblock Diagnostics: Confusing error or lint; hard to understand for new users. F-unboxed_closures `#![feature(unboxed_closures)]` T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@BurntSushi
Copy link
Member

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
Copy link
Member Author

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
Copy link
Contributor

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
Copy link
Contributor

arielb1 commented Sep 2, 2015

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.

@arielb1 arielb1 added A-diagnostics Area: Messages for errors, warnings, and lints and removed A-closures Area: closures (`|args| { .. }`) A-typesystem Area: The type system labels Sep 2, 2015
@alevy
Copy link
Contributor

alevy commented Sep 21, 2015

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
Copy link
Contributor

arielb1 commented Sep 21, 2015

@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
Copy link
Member

eddyb commented Sep 21, 2015

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

@alevy
Copy link
Contributor

alevy commented Sep 21, 2015

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
Copy link
Member

eddyb commented Sep 21, 2015

@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
Copy link
Contributor

alevy commented Sep 21, 2015

@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
Copy link
Member

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
Copy link
Member

eddyb commented Dec 9, 2015

@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
Copy link
Contributor

bfops commented Jan 5, 2016

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
Copy link

J-F-Liu commented Dec 27, 2016

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
Copy link
Member

eddyb commented Dec 27, 2016

@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
Copy link

J-F-Liu commented Dec 28, 2016

@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
Copy link

J-F-Liu commented Feb 6, 2017

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
Copy link
Contributor

rkarp commented May 23, 2017

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
Copy link
Member

eddyb commented May 24, 2017

@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.

@Mark-Simulacrum Mark-Simulacrum added the C-enhancement Category: An issue proposing an enhancement or a PR with one. label Jul 22, 2017
@estebank estebank added D-confusing Diagnostics: Confusing error or lint that should be reworked. D-newcomer-roadblock Diagnostics: Confusing error or lint; hard to understand for new users. labels Oct 11, 2019
@Centril Centril added the F-unboxed_closures `#![feature(unboxed_closures)]` label Nov 5, 2019
@maxammann
Copy link

maxammann commented May 31, 2022

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
Copy link
Member

eddyb commented Jun 4, 2022

@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
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints C-enhancement Category: An issue proposing an enhancement or a PR with one. D-confusing Diagnostics: Confusing error or lint that should be reworked. D-newcomer-roadblock Diagnostics: Confusing error or lint; hard to understand for new users. F-unboxed_closures `#![feature(unboxed_closures)]` T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests