Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to provide type-only argument to a function?

Tags:

rust

In my short Rust experience I ran into this pattern several times, and I'm not sure if the way I solve it is actually adequate...

Let's assume I have some trait that looks like this:

trait Container {
    type Item;
    fn describe_container() -> String;
}

And some struct that implements this trait:

struct ImAContainerType;
struct ImAnItemType;
impl Container for ImAContainerType {
    type Item = ImAnItemType;
    fn describe_container() -> String { "some container that contains items".to_string() }
}

This may be a container that has a knowledge about type of items it contains, like in this example, or, as another example, request which knows what type of response should be returned, etc.

And now I find myself in a situation, when I need to implement a function that takes an item (associated type) and invokes a static function of the container (parent trait). This is the first naive attempt:

fn describe_item_container<C: Container>(item: C::Item) -> String {
    C::describe_container()
}

This does not compile, because associated types are not injective, and Item can have several possible Containers, so this whole situation is ambiguous. I need to somehow provide the actual Container type, but without providing any container data. I may not have the container data itself at all when I invoke this function!

In search for a solution, I find the documentation for std::marker::PhantomData. It says:

PhantomData allows you to describe that a type acts as if it stores a value of type T, even though it does not.

This has to be the Rust's replacement for Haskell's Proxy type, right? Let's try to use it:

fn describe_item_container<C: Container>(container: PhantomData<C>, item: C::Item) -> String {
    C::describe_container()
}

let s = describe_item_container(PhantomData::<PhantomData<ImAContainerType>>, ImAnItemType);
println!("{}", s);

Compiling... Error:

error[E0277]: the trait bound `std::marker::PhantomData<ImAContainerType>: Container` is not satisfied

I ask #rust-beginners and get a response: PhantomData is not meant to be used that way at all! Also I got an advice to simply make a backward associated type link from Item to Container. Something like this:

trait Item {
    type C: Container;
}
fn describe_item_container<I: Item>(item: I) -> String {
    I::C::describe_container()
}

It should work, but makes things much more complicated (especially for cases when item can be placed in different container kinds)...

After a lot more experimentation, I do the following change and everything compiles and works correctly:

let s = describe_item_container(PhantomData::<ImAContainerType>, ImAnItemType);
println!("{}", s);

The change is ::<PhantomData<ImAContainerType>> to ::<ImAContainerType>.

Playground example.

It works, but now I'm completely confused. Is this the correct way to use PhantomData? Why does it work at all? Is there some other, better way to provide type-only argument to a function in Rust?


EDIT: There is some oversimplification in my example, because in that particular case it would be easier to just invoke ImAContainerType::describe_container(). Here is some more complicated case, when the function actually does something with an Item, and still requires container type information.

like image 564
Sergey Mitskevich Avatar asked Oct 26 '16 02:10

Sergey Mitskevich


1 Answers

If you want to pass a type argument to a function, you can just do it. You don't have to leave it out to be inferred.

This is how it looks for your second example (playground):

fn pack_item<C: Container>(item: C::Item) -> ItemPacket {
    ItemPacket {  
        container_description: C::describe_container(),
        _payload: item.get_payload(),
    }
}

fn main() {
    let s = pack_item::<ImAContainerType>(ImAnItemType);
    println!("{}", s.container_description);
    let s = pack_item::<ImAnotherContainerType>(ImAnItemType);
    println!("{}", s.container_description);
}
like image 169
Michał Politowski Avatar answered Nov 15 '22 07:11

Michał Politowski