Home > front end >  Rust: return generic variable
Rust: return generic variable

Time:07-12

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 case img_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 every img_data_member!(A, a) will be replaced with whatever is in the { ... }, with A and a both being from the type ident (= identifier). A becomes the $datatype and a 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

  • Related