Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.4k views
in Technique[技术] by (71.8m points)

rust - Why do I get "the type parameter is not constrained" when creating a blanket implementation for a closure trait (Fn)?

The compiler allows me to write blanket implementation a function like this:

trait Invoke {
    type S;
    type E;

    fn fun(&mut self) -> Result<Self::S, Self::E>;
}

impl<F, S, E> Invoke for F
where
    F: Fn() -> Result<S, E>,
{
    type S = S;
    type E = E;

    fn fun(&mut self) -> Result<S, E> {
        self()
    }
}

but it starts complaining when I try to add a function parameter:

trait Invoke {
    type A;
    type S;
    type E;

    fn fun(&mut self, arg: Self::A) -> Result<Self::S, Self::E>;
}

impl<F, A, S, E> Invoke for F
where
    F: Fn(A) -> Result<S, E>,
{
    type A = A;
    type S = S;
    type E = E;

    fn fun(&mut self, arg: A) -> Result<S, E> {
        self(arg)
    }
}
error[E0207]: the type parameter `A` is not constrained by the impl trait, self type, or predicates
 --> src/lib.rs:9:9
  |
9 | impl<F, A, S, E> Invoke for F
  |         ^ unconstrained type parameter

error[E0207]: the type parameter `S` is not constrained by the impl trait, self type, or predicates
 --> src/lib.rs:9:12
  |
9 | impl<F, A, S, E> Invoke for F
  |            ^ unconstrained type parameter

error[E0207]: the type parameter `E` is not constrained by the impl trait, self type, or predicates
 --> src/lib.rs:9:15
  |
9 | impl<F, A, S, E> Invoke for F
  |               ^ unconstrained type parameter

I cannot understand why these two cases are different. Isn't A a part of constraint signature?

I realized I can rewrite it like the Fn trait declaration, but I still do not get the idea:

trait Invoke<A> {
    type S;
    type E;

    fn fun(&mut self, arg: A) -> Result<Self::S, Self::E>;
}

impl<F, A, S, E> Invoke<A> for F
where
    F: Fn(A) -> Result<S, E>,
{
    type S = S;
    type E = E;

    fn fun(&mut self, arg: A) -> Result<S, E> {
        self(arg)
    }
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Type parameters represent "input" types, while associated types represent "output" types.

Rust allows you to implement multiple instances of a generic trait so long as the combination of type parameters are unique. For example, a single struct Foo could implement PartialEq<Foo> and PartialEq<Bar> together.

In contrast, associated types are assigned by the trait implementation. For example, the Add trait has a type parameter, RHS, and an associated type, Output. For each combination of Self (the type on which the trait is implemented) and RHS, the associated type Output is fixed.

The main reason for using associated types is to reduce the number of type parameters on traits, especially where uses of that trait might have to define a type parameter just to properly bound that trait. However, associated types are not always appropriate; that's why we still have type parameters!


The Fn(Args) -> Output syntax for the Fn trait (and its friends FnMut and FnOnce) hides the underlying implementation of these traits. Here's your first impl again with the unstable "low-level" syntax:

#![feature(unboxed_closures)]

impl<F, S, E> Invoke for F
where
    F: Fn<(), Output = Result<S, E>>,
{
    type S = S;
    type E = E;

    fn fun(&mut self) -> Result<S, E> {
        self()
    }
}

As you can see, the function's result type is an associated type, named Output. Output = Result<S, E> is a predicate, so that satisfies one of the compiler's conditions for type parameters on impl blocks.

Now, here's your second impl with the unstable syntax:

#![feature(unboxed_closures)]

impl<F, A, S, E> Invoke for F
where
    F: Fn<(A,), Output = Result<S, E>>,
{
    type A = A;
    type S = S;
    type E = E;

    fn fun(&mut self, arg: A) -> Result<S, E> {
        self(arg)
    }
}

Here, A is used in Fn's type parameter.

Why is this not valid? In theory1, a single type could have multiple implementations of Fn<Args> with different values of Args. Which implementation should the compiler select in that case? You can only choose one, because A is not passed as a type parameter to Invoke, and thus F can only have a single implementation of Invoke.

1 In practice, you need to use a nightly compiler to do this, because implementing Fn, FnMut or FnOnce directly is an unstable feature. On a stable versions, the compiler will only generate up to one implementation of each of these traits for functions and closures. Also, you could have the same issue with any other trait that has type parameters, even on a stable compiler.

See also:


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

57.0k users

...