web-dev-qa-db-fra.com

Est-il possible d'utiliser `impl Trait` comme type de retour d'une fonction dans une définition de trait?

Est-il possible de définir des fonctions à l'intérieur des traits comme ayant des types de retour impl Trait? Je veux créer un trait qui peut être implémenté par plusieurs structures afin que les fonctions new() de chacune d'entre elles retournent un objet qu'elles peuvent toutes être utilisées de la même manière sans avoir à écrire du code spécifique à chacune .

trait A {
    fn new() -> impl A;
}

Cependant, j'obtiens l'erreur suivante:

error[E0562]: `impl Trait` not allowed outside of function and inherent method return types
 --> src/lib.rs:2:17
  |
2 |     fn new() -> impl A;
  |                 ^^^^^^

Est-ce une limitation de l'implémentation actuelle de impl Trait Ou est-ce que je l'utilise mal?

25
Ameo

Si vous avez seulement besoin de renvoyer le type spécifique pour lequel le trait est actuellement implémenté, vous recherchez peut-être Self.

trait A {
    fn new() -> Self;
}

Par exemple, cela compilera:

trait A {
    fn new() -> Self;
}

struct Person;

impl A for Person {
    fn new() -> Person {
        Person
    }
}

Ou, un exemple plus complet, démontrant l'utilisation du trait:

trait A {
    fn new<S: Into<String>>(name: S) -> Self;
    fn get_name(&self) -> String;
}

struct Person {
    name: String
}

impl A for Person {
    fn new<S: Into<String>>(name: S) -> Person {
        Person { name: name.into() }
    }

    fn get_name(&self) -> String {
        self.name.clone()
    }
}

struct Pet {
    name: String
}

impl A for Pet {
    fn new<S: Into<String>>(name: S) -> Pet {
        Pet { name: name.into() }
    }

    fn get_name(&self) -> String {
        self.name.clone()
    }
}

fn main() {

    let person = Person::new("Simon");
    let pet = Pet::new("Buddy");

    println!("{}'s pets name is {}", get_name(&person), get_name(&pet));
}

fn get_name<T: A>(a: &T) -> String {
    a.get_name()
}

Aire de jeux

En remarque .. J'ai utilisé String ici en faveur de &str références .. pour réduire le besoin de durées de vie explicites et potentiellement une perte de concentration sur la question à l'étude. Je pense que c'est généralement la convention de renvoyer un &str référence lors de l'emprunt du contenu et cela semble approprié ici .. mais je ne voulais pas trop me distraire de l'exemple réel.

12
Simon Whitehead

Comme mentionne trentcl , vous ne pouvez pas actuellement placer impl Trait dans la position de retour d'une méthode de trait.

De RFC 1522 :

impl Trait ne peut être écrit que dans le type de retour d'une fonction autonome ou inhérente-impl, pas dans les définitions de traits ou dans toute position de type non-retour. Ils peuvent également ne pas apparaître dans le type de retour des traits de fermeture ou des pointeurs de fonction, sauf s'ils font eux-mêmes partie d'un type de retour légal.

  • Finalement, nous voudrons permettre à la fonctionnalité d'être utilisée dans les traits [...]

Pour l'instant, vous devez utiliser un objet trait encadré:

trait A {
    fn new() -> Box<dyn A>;
}

Voir également:

Tous les soirs seulement

Si vous souhaitez utiliser des fonctionnalités nocturnes instables, vous pouvez utiliser types existentiels (RFC 2071) :

// 1.40.0-nightly (2019-11-05 1423bec54cf2db283b61)
#![feature(type_alias_impl_trait)]

trait FromTheFuture {
    type Iter: Iterator<Item = u8>;

    fn example(&self) -> Self::Iter;
}

impl FromTheFuture for u8 {
    type Iter = impl Iterator<Item = u8>;

    fn example(&self) -> Self::Iter {
        std::iter::repeat(*self).take(*self as usize)
    }
}

fn main() {
    for v in 7.example() {
        println!("{}", v);
    }
}
27
Shepmaster

Vous pouvez obtenir quelque chose de similaire même dans le cas où il ne renvoie pas Self en utilisant un type associé et nommer explicitement le type de retour:

trait B {}
struct C;

impl B for C {}

trait A {
    type FReturn: B;
    fn f() -> Self::FReturn;
}

struct Person;

impl A for Person {
    type FReturn = C;
    fn f() -> C {
        C
    }
}
7
Jeremy Salwen

Assez nouveau pour Rust, il peut donc être nécessaire de le vérifier.

Vous pouvez paramétrer le type de retour. Cela a des limites, mais ils sont moins restrictifs que de simplement renvoyer Self.

trait A<T> where T: A<T> {
    fn new() -> T;
}

// return a Self type
struct St1;
impl A<St1> for St1 {
    fn new() -> St1 { St1 }
}

// return a different type
struct St2;
impl A<St1> for St2 {
    fn new() -> St1 { St1 }
}

// won't compile as u32 doesn't implement A<u32>
struct St3;
impl A<u32> for St3 {
    fn new() -> u32 { 0 }
}

La limite dans ce cas est que vous ne pouvez renvoyer qu'un type T qui implémente A<T>. Ici, St1 met en oeuvre A<St1>, donc ça va pour St2 à impl A<St2>. Cependant, cela ne fonctionnerait pas avec, par exemple,

impl A<St1> for St2 ...
impl A<St2> for St1 ...

Pour cela, vous devez restreindre davantage les types, avec par exemple.

trait A<T, U> where U: A<T, U>, T: A<U, T> {
    fn new() -> T;
}

mais j'ai du mal à comprendre ce dernier.

0
joelb