Open BurntSushi opened 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.
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
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
.
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.
@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>
.
Each type can implement many traits and T: Trait
implies T: Unsize<Trait>
(if the trait is object-safe).
But if you know T
, you also know E
. Bar<T>
means that you know T
, so E
is never ambiguous in the impl.
@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]>
.
@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.
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?
@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).
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;
}
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
@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>>
.
@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.
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
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
{}
@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
.
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);
}
}
@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;
}
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.
This works fine:
But this:
produces a compile error:
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: