How can I specify lifetimes in associated types?

597 views Asked by At

I'm trying to get each GraphicsContext implementation to return a different implementation of Shader.

pub trait Resources {
    type Shader: shader::Shader;
}

pub trait GraphicsContext {

    type Resources: Resources;

    /// Creates a shader object
    fn create_shader<'a>(&'a self, shader::Stage, source: &str)
        -> Result<<<Self as GraphicsContext>::Resources as Resources>::Shader,
                  shader::CreateError>;

    /// Creates a shader program object
    fn create_shader_program<'a>(&'a self, shaders: Vec<&shader::Shader>)
        -> Result<Box<shader::ShaderProgram + 'a>, shader::ProgramCreateError>;

    // more to come

}

This is so that the create_shader_program method (and other methods) know the concrete type of Shader so that they can call implementation specific methods on the shader object.

I don't want to put these methods (such as setCurrent or attach) into a trait that all implementations must use. Not all graphics APIs use the same system: OpenGL is bind/unbind, Vulkan will be structs/set fields, DirectX is something else etc.

Firstly, I ask is this the right way to structure my engine. I believe that in my framework/application level code that require these Shader objects, I can specify the concrete type based upon the current type of the Context.

// almost certain this doesn't compile, but should be possible in theory
// I'm trying to say:
// the type of the `shader` argument must match to the associated type
// contained within the `context`
fn do_something_with_shader(context: &GraphicsContext,
                            shader: ??**GraphicsContext::Resources::Shader**??)
                           -> Result<Foo, Bar>;

or perhaps:

fn do_something_with_shader<T>(context: &GraphicsContext<T>,
                               shader: ??**T::Shader**??)
    where T: GraphicsContext::Resources -> Result<Foo, Bar>;

Is something like this possible? Hopefully you can see I sort of understand the basic generics (I come from Java), but this is driving me insane (it does feel very hackish).

If that is the right way to go, then there comes a problem with my implementation. rustc wants the associated type to have a lifetime specified.

wrong number of lifetime parameters: expected 1, found 0 [E0107]
opal_driver_gl/src/context.rs:23     type Shader = Shader;

My OpenGLShader struct is actually of type OpenGLShader<'a>, so the error makes sense. My question is where do I get the lifetime from in this bunch of code:

struct OpenGLResources;

impl Resources for OpenGLResources {
    type Shader = OpenGLShader;
}

impl GraphicsContext for OpenGLGraphicsContext {

    type Resources = Resources;

    /// Creates a shader object
    fn create_shader<'a>(&'a self, stage: core_shader::Stage, source: &str)
        -> Result<<<Self as GraphicsContext>::Resources as Resources>::Shader,
                  core_shader::CreateError> {
       // impl goes here
    }

}

I tried attaching a lifetime to OpenGLResources and OpenGLGraphicsContext, which solved the error but then said error: parameter 'a is never used.

So secondly, I ask how I can include that lifetime inside my associated type.

Thanks so much if you can take a look at this. I feel that something like this must be possible to be checked at compile time but I'm pretty new to Rust so I just can't quite grok how to implement it.

1

There are 1 answers

0
neon64 On

I ended up switching to generics, which provided a successful implementation.

Given the Resources<'a>, I can define GraphicsContext like so:

trait GraphicsContext<'a, R: Resources<'a>>

The 2 'a lifetimes above are needed for the Shader<'a> and ShaderProgram<'a> structs inside Resources

Then the OpenGL implementation can be provided. Note that Resources has been changed to OpenGLResources.

                             // replaced here
impl<'a> GraphicsContext<'a, OpenGLResources<'a>> for OpenGLGraphicsContext<'a> {

    fn create_shader(&'a self, ty: Type, source: &str) -> Result<Shader<'a>, ShaderCreationError> {
        // do something
    }

}