2

I have the following problem (it is simplified a bit).

There is a trait which supplies a set of functions which do not use self:

pub trait ImageFormat {
    fn write(data: Vec<[u8; 3]>, path: &str, ...) -> io::Result<()>;
    ...
}

with several implementations.

There also is a struct which uses functions from that trait:

pub struct Images<T: ImageFormat> {
    path: String,
    ...
}

impl<T> Images<T> where T: ImageFormat {
    pub fn setup(path: &str, ...) -> Images<T> {
        Images {
            path: String::from(path),
            ...
        }
    }

    fn write(&mut self, data: Vec<[u8; 3]>, ...) -> io::Result<()> {
        T::write(data, &self.path[..], ...)
    }
    ...
}

This does not compile, because the struct does not have a field of type T. It works if I do this, but it feels like a hack:

pub struct Images<T: ImageFormat> {
    _image_format: Option<T>,
    path: String,
    ...
}

impl<T> Images<T> where T: ImageFormat {
    pub fn setup(path: &str, ...) -> Images<T> {
        Images {
            _image_format: None,
            path: String::from(path),
            ...
        }
    }
...
}

Is there any idiomatic way of doing this?

myszon
  • 414
  • 4
  • 8
  • why don't just write "struct Images" if struct doesn't need `T` ? Please describe more what you want. If you really want to "force" it, you can use [phamtom data](https://doc.rust-lang.org/std/marker/struct.PhantomData.html), but I feel that you should not use it in your case. – Stargateur Dec 30 '18 at 12:39
  • The struct does not need T, but the methods like write on Images do need it. All I need in practice is the type, as these functions do not use self. Phantom data seems to be what I was looking for. – myszon Dec 30 '18 at 17:43
  • That doesn't add information, I still think you use the bad tool for your problem. – Stargateur Dec 30 '18 at 23:38

1 Answers1

2

PhantomData can be used "to mark things that "act like" they own a T".

So, you could write:

pub struct Images<T: ImageFormat> {
    path: String,
    phantom: PhantomData<T>, // mark that Image "acts like" it owns a T
}

In initialization, you simply provide a PhantomData for the respective field:

Images {
    path: ...
    phantom: PhantomData,
}

As mentioned by others, you may be better off without the type parameter in the first place, but I have had cases where they seem perfectly reasonable to me (e.g. if T provides functions that do not take a self).

phimuemue
  • 34,669
  • 9
  • 84
  • 115
  • Yes, this is exactly my case. T provides functions that do not take self. – myszon Dec 30 '18 at 17:35
  • "if T provides functions that do not take a self", and ? just add the generic to the impl not the struct, I don't understand the use case. On the contrary I think that typical bad use case of phantom data. – Stargateur Dec 30 '18 at 23:36
  • 1
    The actual use case (https://github.com/myszon/raytracer/tree/master/src/format) is a bit more complex. Instances of `Images` are not used directly, but as trait objects to choose behaviour in runtime. So if generic was just added to the `impl`, then the compiler would not be able to chose the correct variant of `write` when called from a trait object. – myszon Jan 01 '19 at 03:01