Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to alias an impl trait?

Tags:

rust

traits

I have many functions of the following type signature:

fn f() -> impl Fn(u32) -> u32 { 
    |x: u32| x 
}

How can I give a name to Fn(u32) -> u32 so that I don't have to repeat it? Although I can do type X = Fn(u32) -> u32;, Rust will not let me use this because it is a type and not a trait. Must I wait for trait_alias or can I do something else?

like image 969
Listerone Avatar asked Sep 14 '19 16:09

Listerone


People also ask

What does impl mean in Rust?

The impl keyword is primarily used to define implementations on types. Inherent implementations are standalone, while trait implementations are used to implement traits for types, or other traits. Functions and consts can both be defined in an implementation.

What is type keyword in Rust?

Keyword typeDefine an alias for an existing type. The syntax is type Name = ExistingType; .

What is opaque type rust?

Opaque types are a kind of type alias. They are called opaque because, unlike an ordinary type alias, most Rust code (e.g., the callers of as_u32s ) doesn't know what type AsU32sReturn represents. It only knows what traits that type implements (e.g., IntoIterator<Item = u32> ).


1 Answers

You're exactly right. impl X requires X to be a trait, and it's impossible to have proper trait aliases until trait aliases land. When that happens you'll be able to do this:

#![feature(trait_alias)]

trait X = Fn(u32) -> u32;

fn f() -> impl X {
    |x: u32| x
}

(playground)


Alternatively, when Permit impl Trait in type aliases lands, you'll be able to make impl trait a type alias. This is slightly different though. When we alias with type X = impl Trait, the compiler will ensure that every usage of X is actually the same concrete type. That would mean that, in your case, you wouldn't be able to use this with multiple different closures, since every closure has its own unique type.

#![feature(type_alias_impl_trait)]

type X = impl Fn(u32) -> u32;

fn f() -> X {
    |x: u32| x
}

(playground)

However, this won't compile.

#![feature(type_alias_impl_trait)]

type X = impl Fn(u32) -> u32;

fn f() -> X {
    |x: u32| x
}

// Even a closure with exactly the same form has a different type.
fn g() -> X {
    |x: u32| x
}

The error is

error: concrete type differs from previous defining opaque type use
  --> src/lib.rs:10:1
   |
10 | / fn g() -> X {
11 | |     |x: u32| x
12 | | }
   | |_^ expected `[closure@src/lib.rs:7:5: 7:15]`, got `[closure@src/lib.rs:11:5: 11:15]`
   |
note: previous use here
  --> src/lib.rs:6:1
   |
6  | / fn f() -> X {
7  | |     |x: u32| x
8  | | }
   | |_^

(playground)

This is in contrast to trait aliases, which would allow a different concrete type to be used with every function returning impl TraitAlias. See the RFCs that introduced this syntax and existential types in general for more.


Until one of those two features lands, you can get similar behavior to the trait alias with what is essentially a hack. The idea is to make a new trait which is essentially equivalent to the original trait, but has a shorter name.

// This trait is local to this crate,
// so we can implement it on any type we want.
trait ShortName: Fn(u32) -> u32 {}

// So let's go ahead and implement `ShortName`
// on any type that implements `Fn(u32) -> u32`.
impl<T: Fn(u32) -> u32> ShortName for T {}

// We can use `ShortName` to alias `Fn(u32) -> u32`.
fn f() -> impl ShortName {
    |x: u32| x
}

// Moreover, the result of that can be used in places
// that expect `Fn(u32) -> u32`.
fn g<T: Fn(u32) -> u32>(x: &T) -> u32 {
    x(6_u32)
}

fn main() {
    // We only know that `x` implements `ShortName`,
    let x = f();
    // But we can still use `x` in `g`,
    // which expects an `Fn(u32) -> u32` argument
    let _ = g(&x);
}

(playground)

like image 64
SCappella Avatar answered Sep 20 '22 00:09

SCappella