5

Is it possible to create an instance of a generic parameter type in Rust?

I'm coming from a C++ background where it's perfectly valid to use template types to create types in the actual function body.

I'm trying to create a variable with type T in this function but I'm not sure how.

I just want to be able to create an object of type T, load it, and then insert it into the HashMap:

fn load<T>(&mut self, id: String, path: String)
    where T: AssetTrait + 'static
{
    // let asset : T = T; this doesn't work?

    asset.load(path);

    // self.assets.insert(id, Box::new<T>(asset));
}

Here is all my code:

trait AssetTrait {
    fn load(&self, path: String) {
        // Do nothing
        // Implement this in child asset object
    }
}

struct AssetManager {
    assets: HashMap<String, Box<AssetTrait + 'static>>,
}

impl AssetManager {
    fn new() -> AssetManager {
        let asset_manager = AssetManager { assets: HashMap::new() };

        return asset_manager;
    }

    fn load<T>(&mut self, id: String, path: String)
        where T: AssetTrait + 'static
    {
        // let asset : T = T; this doesn't work?

        asset.load(path);

        // self.assets.insert(id, Box::new<T>(asset));
    }
}

1 Answer 1

5

In C++ when you declare a variable like T asset; you are assuming that T has a default constructor (compile time version of duck typing). It is an compiler error to instantiated T with a type that does not have a default constructor, but it's ok if no such instantiation happens.

In Rust you cannot "assume" that a type parameter supports an operation. You have to specify the supported operations with bounds on the type parameter.

That said, you have to options:

Define your own constructor like associated function in AssetTrait

For example, you can declare load as an associated function that do not take a self parameter and returns Self, and call T::load(path) to instantiate T:

use std::collections::HashMap;

trait AssetTrait {
    fn load(path: String) -> Self;
}

struct AssetManager {
    assets: HashMap<String, Box<AssetTrait + 'static>>,
}

impl AssetManager {
    fn new() -> AssetManager {
        let asset_manager = AssetManager { assets: HashMap::new() };
        return asset_manager;
    }

    fn load<T>(&mut self, id: String, path: String)
        where T: AssetTrait + 'static
    {
        let asset = T::load(path);
        self.assets.insert(id, Box::new(asset));
    }
}

Use a predefined trait that has a constructor like associated function

In Rust, the Default trait is used for this purpose:

use std::collections::HashMap;

trait AssetTrait {
    fn load(&mut self, path: String);
}

struct AssetManager {
    assets: HashMap<String, Box<AssetTrait + 'static>>,
}

impl AssetManager {
    fn new() -> AssetManager {
        let asset_manager = AssetManager { assets: HashMap::new() };
        return asset_manager;
    }

    fn load<T>(&mut self, id: String, path: String)
        where T: Default + AssetTrait + 'static
    {
        let mut asset = T::default();
        asset.load(path);
        self.assets.insert(id, Box::new(asset));
    }
}
Sign up to request clarification or add additional context in comments.

4 Comments

I got it to work but I had to change the "asset" variable in the second example to be mutable for the load function to work correctly.
That makes sense, load will modify self.
Tiny note: every fn deccared in an impl block is an associated fn. we have a more specific name for those that take self, methods, but methods are associated functions.
@SteveKlabnik Thanks Steve. I updated the answer and this lapse was removed.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.