如何从一个方法中返回一个 trait 的实例?

44
我正在尝试创建一个函数,该函数返回实现Shader特质的实例。以下是我的大幅简化的代码:
trait Shader {}

struct MyShader;
impl Shader for MyShader {}

struct GraphicsContext;

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

fn main() {}

然而,我收到了以下错误提示:
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

编译器的更新版本出现了这个错误:

error[E0277]: the size for values of type `(dyn Shader + 'static)` cannot be known at compilation time
 --> src/main.rs:9:32
  |
9 |     fn create_shader(&self) -> Shader {
  |                                ^^^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `(dyn Shader + 'static)`
  = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
  = note: the return type of a function must have a statically known size

这很有道理,因为编译器不知道trait的大小,但我找不到修复此问题的推荐方法。 据我所知,使用&返回引用是行不通的,因为引用将超出其创建者的生命周期。 也许我需要使用Box<T>
4个回答

51

Rust 1.26及以上版本

现在已经存在impl Trait:

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

它确实有一些限制,例如无法在 trait 方法中使用,也不能在具体返回类型条件性的情况下使用。在这些情况下,您需要使用下面的 trait 对象答案。

Rust 1.0 及更高版本

您需要返回某种类型(关键字为 dyn)的 trait 对象,例如 &dyn TBox<dyn T>,在这种情况下,&dyn T 是不可能的。

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

请参阅:

2
指出impl Trait实际上创建了一个通用函数,它使用静态多态性。因此,如果目标是具有动态多态性(即在运行时更改行为),仍然最好返回一个(装箱的)特质对象,即Box<dyn Shader> - JoaoBapt
1
为什么无法从trait方法返回impl Shader?我处于这样一种情况:我不需要条件返回类型,因为实现trait的每个结构体只会返回特定的变量。但是我仍然被迫使用Box<dyn Shader>方法,因为我需要使用trait方法。 - Meet Sinojia
8
简而言之,因为该语言不支持它。该语言尚不支持此功能,因为这需要更多我们尚未完成的类型系统机制。 - Steve Klabnik
@SteveKlabnik,你知道编程语言何时可能支持从特质方法返回impl Shader吗? - Sid
2
暂无时间表。我们需要首先引入一个名为“GATs”的功能。 - Steve Klabnik

7
我认为这是您正在搜索的内容:使用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());
}

2
我认为你可以使用泛型和静态分派(我不知道这些是否是正确的术语,我只是看到别人用过)来创建类似于这样的东西。这并不完全是“作为特征返回”,但它让函数通用地使用特征。在我看来,语法有点晦涩,很容易被忽略。我在Using generic iterators instead of specific list types上询问了有关返回Iterator特征的问题。变得丑陋起来。在播放器中:
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());
}

即使您没有添加 as_trait() 函数,仍然可以调用 get_name 方法。 - Djvu

0

返回 Box<shader>。由于类型的大小必须固定,因此您必须使用 box 智能指针来限制对象。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接