web-dev-qa-db-fra.com

Comment puis-je retourner une instance d'un trait d'une méthode?

J'essaie de créer une fonction qui retourne une instance du trait Shader. Voici mon code radicalement simplifié:

trait Shader {}

struct MyShader;
impl Shader for MyShader {}

struct GraphicsContext;

impl GraphicsContext {
    fn create_shader(&self) -> Shader {
        let shader = MyShader;
        shader
    }
}

fn main() {}

Cependant, je reçois l'erreur suivante:

error[E0277]: the trait bound `Shader + 'static: std::marker::Sized` is not satisfied
  --> src/main.rs:10:32
   |
10 |     fn create_shader(&self) -> Shader {
   |                                ^^^^^^ `Shader + 'static` does not have a constant size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `Shader + 'static`
   = note: the return type of a function must have a statically known size

Cela a du sens car le compilateur ne connaît pas la taille du trait, mais je ne trouve nulle part le moyen recommandé pour résoudre ce problème. Redonner une référence avec & ne fonctionnerait pas autant que je sache, car la référence survivrait à la vie de son créateur.

Peut-être dois-je utiliser Box<T>?

19
neon64

Rouille 1.26 et plus

impl Trait existe maintenant :

fn create_shader(&self) -> impl Shader {
    let shader = MyShader;
    shader
}

Il comporte des limitations, telles que le fait de ne pas pouvoir être utilisé dans une méthode de trait et qu'il ne peut pas être utilisé lorsque le type de retour concret est conditionnel. Dans ces cas, vous devez utiliser la réponse d'objet trait ci-dessous.

Rouille 1.0 et plus

Vous devez renvoyer un objet trait de quelque sorte, tel que &T ou Box<T>, et vous avez raison de dire que &T est impossible dans cet exemple:

fn create_shader(&self) -> Box<Shader> {
    let shader = MyShader;
    Box::new(shader)
}

Voir également:

23
Steve Klabnik

Je pense que c'est ce que vous cherchiez; une usine simple implémentée à Rust :

pub trait Command {
    fn execute(&self) -> String;
}

struct AddCmd;
struct DeleteCmd;

impl Command for AddCmd {
    fn execute(&self) -> String {
        "It add".into()
    }
}

impl Command for DeleteCmd {
    fn execute(&self) -> String {
        "It delete".into()
    }
}

fn command(s: &str) -> Option<Box<Command + 'static>> {
    match s {
        "add" => Some(Box::new(AddCmd)),
        "delete" => Some(Box::new(DeleteCmd)),
        _ => None,
    }
}

fn main() {
    let a = command("add").unwrap();
    let d = command("delete").unwrap();
    println!("{}", a.execute());
    println!("{}", d.execute());
}
4
Cristian Oliveira

Je pense que vous pouvez utiliser les génériques et la répartition statique (je ne sais pas si ce sont les bons termes, j'ai juste vu quelqu'un d'autre les utiliser) pour créer quelque chose comme ça.

Ce n'est pas exactement "redevenir un trait", mais laisser les fonctions utiliser les traits de manière générique. La syntaxe est un peu obscure, à mon avis, donc facile à manquer.

J'ai demandé Utiliser des itérateurs génériques au lieu de types de liste spécifiques pour retourner le trait Iterator. Ça devient moche. 

Dans la cour de récréation :

struct MyThing {
    name: String,
}

trait MyTrait {
    fn get_name(&self) -> String;
}

impl MyTrait for MyThing {
    fn get_name(&self) -> String {
        self.name.clone()
    }
}

fn as_trait<T: MyTrait>(t: T) -> T {
    t
}

fn main() {
    let t = MyThing {
        name: "James".to_string(),
    };
    let new_t = as_trait(t);

    println!("Hello, world! {}", new_t.get_name());
}
0
jocull