44

I'm trying to create a function that returns an instance of the Shader trait. Here is my drastically simplified code:

trait Shader {}

struct MyShader;
impl Shader for MyShader {}

struct GraphicsContext;

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

fn main() {}

However I receive the following error:

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

Newer versions of the compiler have this error:

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

This makes sense as the compiler doesn't know the size of the trait, but nowhere can I find the recommended way of fixing this. Passing back a reference with & wouldn't work as far as I know because the reference would outlive the lifetime of its creator.

Perhaps I need to use Box<T>?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
neon64
  • 1,486
  • 2
  • 12
  • 13

4 Answers4

49

Rust 1.26 and up

impl Trait now exists:

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

It does have limitations, such as not being able to be used in a trait method and it cannot be used when the concrete return type is conditional. In those cases, you need to use the trait object answer below.

Rust 1.0 and up

You need to return a trait object of some kind (keyword dyn), such as &dyn T or Box<dyn T>, and you're right that &dyn T is impossible in this instance:

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

See also:

Zoltan Zarlow
  • 97
  • 1
  • 8
Steve Klabnik
  • 14,521
  • 4
  • 58
  • 99
  • 2
    It is good to point that `impl Trait` actually creates a *generic* function, which uses *static* polymorphism. So, if the objective is having dynamic polymorphism (aka having the behavior change at runtime), it is still better to return a (boxed) trait object, aka `Box`. – JoaoBapt Jan 07 '20 at 18:37
  • 1
    Why can't `impl Shader` be returned from a trait method? I am in a situation where I don't need a conditional return type, as each struct that implements a trait would only return a particular variant. But I am still forced to use `Box` approach because I need to use a trait method. – Meet Sinojia Aug 20 '20 at 13:47
  • 8
    In short, because the language does not support it. The language does not yet support it because this requires more type system machinery that we have not finished yet. – Steve Klabnik Aug 21 '20 at 16:00
  • @SteveKlabnik do you know when the language might support returning impl Shader from a trait method? – Sid May 23 '21 at 23:59
  • 2
    No timeline yet. We need a feature called "GATs" first. – Steve Klabnik May 25 '21 at 14:12
7

I think this is what you were searching for; a simple factory implemented in 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());
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
2

I think you can use generics and static dispatch (I have no idea if those are the right terms, I just saw someone else use them) to create something like this.

This isn't exactly "returning as a trait", but it is letting functions use traits generically. The syntax is a little obscure, in my opinion, so it's easy to miss.

I asked Using generic iterators instead of specific list types about returning the Iterator trait. It gets ugly.

In the playground:

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());
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
jocull
  • 20,008
  • 22
  • 105
  • 149
  • even if you do not add the as_trait() function, you can still call the `get_name` method. – Djvu Apr 29 '16 at 13:52
0

return Box<shader>. As the size of the type must be fixed so you've to bound the object using box smart pointer.

necimye
  • 59
  • 1
  • 3