How to return implemented type from trait fn


#1

Note: I’m very new to Rust and come from a PHP background, so know absolutely nothing about programming! :smile:

I’m trying to create a file loader for some config files. Each config file structure is different, thus each file is represented by its own struct. The problem I’m having is that I don’t know how to create a trait that I can implement on each struct that will return a new struct of that type. Best I illustrate with an example (I’ve put a placeholder in the bit where I’m getting confused):

trait Config {
    fn load<T: ***VARIABLE_STRUCT_TYPE***>(file_path: String) -> Result<T, ConfigError> {
        // Load file and initialise decoder...
        let config = try!(T::decode(&mut decoder));
        Ok(config)
    }
}

#[derive(RustcDecodable)]
struct SystemConfig {
    //...
}

impl Config for SystemConfig {}

#[derive(RustcDecodable)]
struct ProjectConfig {
    //...
}

impl Config for ProjectConfig {}

EDIT: Sorry here’s a dead simple usage example:

let system_config = SystemConfig::load("/path/to/file".to_string());
let project_config = ProjectConfig::load("/path/to/file".to_string());

I’d really appreciate any help! I’ve read the whole Rust book, Googled around for hours and kicked multiple inanimate objects. I’ve based my solution on this post (Improving the loading of TOML to form a struct?) though he’s using a single struct to represent his config. I essentially want to abstract the struct that represents the format of the config file so that I can reuse the code on multiple structs, hence multiple config files.

Cheers,

Pete.


#2

What you need here is associated type to declare your file type.
The normal generics you have currently allow the caller to decide the type they want.
Associated types allow the implementor of trait to decide type they want.
In this case SystemCofig wants different config file type to what ProjectConfig wants.

trait Config {
    type ConfigFile;
    fn load(file_path: String) -> Result<ConfigFile, ConfigError> {
        // Load file and initialise decoder...
        let config = try!(ConfigFile::decode(&mut decoder));
        Ok(config)
    }
}

#3

Oh great, it’s a whole chapter in the Rust book that “I read” (facepalm)!

Thanks mate, you’re a legend. Exactly what I need.


#4

Yeah. The graph example there isn’t exactly the best one. I myself actually learned about it via irc/reading random blogs.


#5

I’ve hit another roadblock that might be related to the associated type I’m using. Each config struct derives RustcDecodable, so I’ve added that to the type. However when I try to call decode on the type, it gives me an error.

Here’s the code:

pub trait Config {
    type ConfigFile: Decodable;
    
	fn load<'a>(file_path: String) -> Result<&'a Self::ConfigFile, ConfigLoadError> {
		let mut config_file = try!(fs::File::open(&file_path));
		let mut config_content = String::new();
		try!(config_file.read_to_string(&mut config_content));

		let mut parser = toml::Parser::new(&config_content);
		match parser.parse() {
		    Some(toml) => {
		        let toml = toml::Value::Table(toml);
		        let mut decoder = toml::Decoder::new(toml);
        		let config = try!(&Self::ConfigFile::decode(&mut decoder));
        		
        		Ok(config)
	        },
		    None => Err(ConfigLoadError::ParseError(parser.errors.pop().unwrap())),
		}
	}
}

And here’s the error:

:3:1: 3:39 error: mismatched types:
expected &core::result::Result<<Self as config_loader::Config>::ConfigFile, _>,
found core::result::Result<_, _>
(expected &-ptr,
found enum core::result::Result) [E0308]

Any ideas?


#6

You are trying to return a borrow of the config file.
This is not possible in this case, because the config file you are creating will be destroyed when the load function ends.
Only times you can return borrow from a function is:

  1. You borrow something from parameter of the function (which can be self when you have a method)
  2. You return something that’s 'static
  3. Do something terrible with unsafe

What you want to do is actually return Result<Self::ConfigFile, ConfigLoadError>


#7

Thanks again mate. I used a borrow because I mindlessly followed the book, rather than thinking about whether I actually needed it.

With your help I finally satisfied the compiler, so I’d say it’s problem solved! Cheers.