Matt Joiner
Matt Joiner

Reputation: 118680

Require type parameter to be a struct

I have a function that I've specified a type parameter T on, which I assume to be a struct. I then collect an iterator of T into a Vec.:

pub fn get_matches<T>(
    &self,
) -> Vec< T> 
{
    ...
    some_iter.
        .map(|(k, _v)| T{key:(**k).to_string(), hits: 0})
        .collect()
}

I get this error:

96 |             .map(|(k, _v)| T{key:(**k).to_string(), hits: 0})
   |                            ^ not a struct, variant or union type

I've tried having the return type be a type parameter, but I can't work out how to get the Vec's element type and instantiate that either. I just want to produce elements of a certain shape (that is with a key: string, and hits: usize) and return a container of whatever the caller is expecting.

Upvotes: 1

Views: 1057

Answers (1)

Inline
Inline

Reputation: 2863

Rust generics are different from C++ templates. There are valid reasons why they are different, such as better error reporting and faster compilation.

In C++, template (if we oversimplify) types are not checked at the initial invocation stage, rather the template continues to expand until it is either successful, or runs into an operation that is not supported by that specific type.

While in Rust, types are checked immediately. The specification must be given up-front, which means that any error is caught at the call site, as opposed to deep in a template expansion.

Your specific example assumes that every T should have fields key and hits, but T can be anything starting from primitive types to non-public structs that don't have key or hits fields.

The Rust way of doing things is to declare a trait and use it to specify that the type has certain constructor function for you. In this context the trait will be a zero-cost abstraction, because of static polymorphism.

trait StringConstructable {
    fn new(string: String) -> Self;
}

struct Test {
    key: String,
    hits: usize
}

impl StringConstructable for Test {
    fn new(string: String) -> Self {
        Test {
            key: string,
            hits: 0
        }
    }
}

fn test<T: StringConstructable>() -> T {
    T::new("test".to_string())
}

Playground link

Or implement and require From<String> for your T.

struct Test {
    key: String,
    hits: usize
}

impl From<String> for Test {
    fn from(string: String) -> Test {
        Test {
            key: string,
            hits: 0
        }
    }
}

fn test<T: From<String>>() -> T {
    T::from("test".to_string())
}

Playground link

Upvotes: 5

Related Questions