简体   繁体   English

Rust:返回泛型变量

[英]Rust: return generic variable

Is it possible in Rust to return generic type as collect() does?.在 Rust 中是否可以像collect()那样返回泛型类型? 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?我可以在函数中返回由 typ 参数指定的特定结构字段,以便我不需要为每个字段编写每个函数吗? Like that:像那样:

let chnk: &Option<IHDR_t> = img.chunk(ChunkType::IHDR);

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.但是你必须match或者if let你的chunk方法的结果。

If you convert your enum to an enum-like trait / struct construct, you can derive the return type automatically:如果将enum转换为类似 enum 的trait / struct构造,则可以自动派生返回类型:

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:或者更进一步,您可以直接跳过准枚举类型并从返回类型派生 getter 方法:

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.您使用哪一个当然取决于您以及您想要公开哪个 API。

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() .最后一个示例中的想法是,如果您有一个函数fn chunk<C: ChunkType>(&self) -> &C ,那么 Rust 足够聪明,可以通过从您分配返回的变量反向传播类型来确定C应该是什么值,比如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). img.chunk()可以返回的所有类型都必须以某种方式从Img派生,因此我引入了一个trait ChunkType能够从Img借用自身(通过from_img函数)。

Every chunk type then has to impl this trait.然后每个块类型都必须impl这个特征。 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 { ... } macro_rules! img_data_member { ... } : This defines the name of the macro, in this case img_data_member . macro_rules! img_data_member { ... } :这定义了宏的名称,在本例中为img_data_member
  • ($datatype: ident, $imgmember: ident) => { ... } : This defines a macro replacement rule. ($datatype: ident, $imgmember: ident) => { ... } :这定义了一个宏替换规则。 A macro can have several rules.一个宏可以有多个规则。 Rules always contain the input , in this case ($datatype: ident, $imgmember: ident) , and the output, here in the { ... } .规则总是包含输入,在这种情况下($datatype: ident, $imgmember: ident)和输出,在{ ... }中。 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).这意味着每个img_data_member!(A, a)将被替换为{ ... }中的任何内容,其中Aa都来自类型ident (= 标识符)。 A becomes the $datatype and a becomes the $imgmember . A成为$datatype并且a成为$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. Rust 宏最酷的地方在于编译器实际上知道发生了什么。 So you get real, helpful error messages instead of the page long nonsense that C++ compilers used to output.因此,您将获得真实、有用的错误消息,而不是 C++ 编译器用来输出的冗长废话。

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`

I figured it out, I wrapped everything in ADT Enum:我想通了,我将所有内容都包装在 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之后我可以得到包含在包含选项的枚举中的值,并且在所有这些操作之后最终得到值

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM