Is it possible in Rust to return generic type as collect() does?. For example: I have this function
pub fn chunk<T>(&self, typ: ChunkType) -> &Option<T> {
match typ {
PLTE => (),
GAMA => (),
CHRM => (),
SRGB => (),
ICCP => (),
TEXT => (),
ZTXT => (),
ITXT => (),
BKGD => (),
PHYS => (),
SBIT => (),
SPLT => (),
HIST => (),
TIME => (),
IHDR => &self.IHDR,
TRNS => (),
}
}
and Enum
enum ChunkType {
PLTE,
GAMA,
CHRM,
SRGB,
ICCP,
TEXT,
ZTXT,
ITXT,
BKGD,
PHYS,
SBIT,
SPLT,
HIST,
TIME,
IHDR,
TRNS,
}
Can I return specific struct field specified by typ argument in function so that I don't need to write every function for each field? Like that:
let chnk: &Option<IHDR_t> = img.chunk(ChunkType::IHDR);
CodePudding user response:
Your answer is kinda right, you could do it like that. BUT you then have to match
or if let
the result of your chunk
method.
If you convert your enum
to an enum-like trait
/struct
construct, you can derive the return type automatically:
fn main() {
let img = Img {
hdr: IHDR,
txt: ITXT,
};
// No type annotation needed
// - hdr is of type &IHDR
// - txt is of type &ITXT
let hdr = img.chunk::<HDR>();
let txt = img.chunk::<TXT>();
}
// The actual data
struct IHDR;
struct ITXT;
// The image holding the data
struct Img {
hdr: IHDR,
txt: ITXT,
}
impl Img {
pub fn chunk<C: ChunkType>(&self) -> &C::Data {
C::from_img(&self)
}
}
// The enum-like trait
trait ChunkType {
type Data;
fn from_img(img: &Img) -> &Self::Data;
}
// The enum-member like structs
struct HDR;
struct TXT;
impl ChunkType for HDR {
type Data = IHDR;
fn from_img(img: &Img) -> &Self::Data {
&img.hdr
}
}
impl ChunkType for TXT {
type Data = ITXT;
fn from_img(img: &Img) -> &Self::Data {
&img.txt
}
}
Of course, if you have many types, you could put all of that in a macro:
fn main() {
let img = Img {
hdr: IHDR,
txt: ITXT,
};
// No type annotation needed
// - hdr is of type &IHDR
// - txt is of type &ITXT
let hdr = img.chunk::<HDR>();
let txt = img.chunk::<TXT>();
}
// The actual data
struct IHDR;
struct ITXT;
// The image holding the data
struct Img {
hdr: IHDR,
txt: ITXT,
}
impl Img {
pub fn chunk<C: ChunkType>(&self) -> &C::Data {
C::from_img(&self)
}
}
// The enum-like trait
trait ChunkType {
type Data;
fn from_img(img: &Img) -> &Self::Data;
}
macro_rules! img_data_member {
($datatype: ident, $enumtype: ident, $imgmember: ident) => {
struct $enumtype;
impl ChunkType for $enumtype {
type Data = $datatype;
fn from_img(img: &Img) -> &Self::Data {
&img.$imgmember
}
}
};
}
img_data_member!(IHDR, HDR, hdr);
img_data_member!(ITXT, TXT, txt);
// ...
Or even further, you could skip the quasi-enum type directly and derive the getter method from the return type:
fn main() {
let img = Img {
hdr: IHDR,
txt: ITXT,
};
// No generic annotation needed.
// What `.chunk` does is derived from the output type
let hdr: &IHDR = img.chunk();
let txt: &ITXT = img.chunk();
}
// The actual data
struct IHDR;
struct ITXT;
// The image holding the data
struct Img {
hdr: IHDR,
txt: ITXT,
}
impl Img {
pub fn chunk<C: ChunkType>(&self) -> &C {
C::from_img(&self)
}
}
// The enum-like trait
trait ChunkType {
fn from_img(img: &Img) -> &Self;
}
macro_rules! img_data_member {
($datatype: ident, $imgmember: ident) => {
impl ChunkType for $datatype {
fn from_img(img: &Img) -> &Self {
&img.$imgmember
}
}
};
}
img_data_member!(IHDR, hdr);
img_data_member!(ITXT, txt);
// ...
Which one you use is of course up to you and which API you want to expose.
Explanation
The idea in this last example is that if you have a function fn chunk<C: ChunkType>(&self) -> &C
, then Rust is smart enough to figure out what C
should be by backpropagating the type from the variable you assing the return value into, like let hdr: &IHDR = img.chunk()
.
From there, it's kinda straight forward. All your types that img.chunk()
can return must be somehow derivable from Img
, so I introduced a trait ChunkType
that has the capability to borrow itself from Img
(via the from_img
function).
Every chunk type then has to impl
this trait.
As you have a bunch of traits, I wrote a macro to reduce code duplication.
Macros are not that hard. Let's disect this macro:
macro_rules! img_data_member {
($datatype: ident, $imgmember: ident) => {
impl ChunkType for $datatype {
fn from_img(img: &Img) -> &Self {
&img.$imgmember
}
}
};
}
macro_rules! img_data_member { ... }
: This defines the name of the macro, in this caseimg_data_member
.($datatype: ident, $imgmember: ident) => { ... }
: This defines a macro replacement rule. A macro can have several rules. Rules always contain the input, in this case($datatype: ident, $imgmember: ident)
, and the output, here in the{ ... }
. This means that everyimg_data_member!(A, a)
will be replaced with whatever is in the{ ... }
, withA
anda
both being from the typeident
(= identifier).A
becomes the$datatype
anda
becomes the$imgmember
.
The entire rest is more-or-less just a simple replacement:
img_data_member!(A, a);
will be replaced with
impl ChunkType for A {
fn from_img(img: &Img) -> &Self {
&img.a
}
}
The cool thing about Rust macros is that the compiler is actually aware of what happens. So you get real, helpful error messages instead of the page long nonsense that C compilers used to output.
For example:
img_data_member!(IHDR, hdr2);
Produces the following error:
error[E0609]: no field `hdr2` on type `&Img`
--> src/main.rs:44:24
|
44 | img_data_member!(IHDR, hdr2);
| ^^^^ help: a field with a similar name exists: `hdr`
CodePudding user response:
I figured it out, I wrapped everything in ADT Enum:
enum ChunkType<'a> {
PLTE(&'a Option<PLTE_t>),
GAMA(&'a Option<gAMA_t>),
CHRM(&'a Option<cHRM_t>),
SRGB(&'a Option<sRGB_t>),
ICCP(&'a Option<iCCP_t>),
TEXT(&'a Option<tEXt_t>),
ZTXT(&'a Option<zTXt_t>),
ITXT(&'a Option<iTXt_t>),
BKGD(&'a Option<bKGD_t>),
PHYS(&'a Option<pHYs_t>),
SBIT(&'a Option<sBIT_t>),
SPLT(&'a Option<sPLT_t>),
HIST(&'a Option<hIST_t>),
TIME(&'a Option<tIME_t>),
IHDR(&'a Option<IHDR_t>),
TRNS(&'a Option<tRNS_t>),
}
and in method
pub fn chunk(&self, typ: &str) -> ChunkType {
match typ {
"ihdr" => ChunkType::IHDR(&self.IHDR),
"plte" => ChunkType::PLTE(&self.PLTE),
"gama" => ChunkType::GAMA(&self.gAMA),
"chrm" => ChunkType::CHRM(&self.cHRM),
"srgb" => ChunkType::SRGB(&self.sRGB),
"iccp" => ChunkType::ICCP(&self.iCCP),
"text" => ChunkType::TEXT(&self.tEXt),
"ztxt" => ChunkType::ZTXT(&self.zTXt),
"itxt" => ChunkType::ITXT(&self.iTXt),
"bkgd" => ChunkType::BKGD(&self.bKGD),
"phys" => ChunkType::PHYS(&self.pHYs),
"sbit" => ChunkType::SBIT(&self.sBIT),
"splt" => ChunkType::SPLT(&self.sPLT),
"hist" => ChunkType::HIST(&self.hIST),
"time" => ChunkType::TIME(&self.tIME),
"trns" => ChunkType::TRNS(&self.tRNS),
_ => panic!("Undefined type"),
}
}
after that I can get value wrapped in enum which contains Option, and after all those manipulations finally get the value