繁体   English   中英

计算结构 Rust 中“无”类型的字段数

[英]Calculate number of fields that are of type "None" in a struct Rust

我正在研究一个宏,它计算结构上在运行时类型为 None 的字段数。 我有一个计算结构字段数的实现。 源代码如下。

use std::rc::Rc;
use std::cell::Cell;

macro_rules! generate {
    ($name:ident {$($field:ident : $t:ty),+}) => {
        struct $name { $($field: $t),+ }
        impl $name {
            fn field_count(&self) -> usize {
                generate!(@count $($field),+)
            }
        }
    };
    (@count $t1:tt, $($t:tt),+) => { 1 + generate!(@count $($t),+) };
    (@count $t:tt) => { 1 };
}

generate! { test1 { num: i32, s: Option<String> }}
generate! { test2 { num: i32, s: String }}

fn main() {
    let s = test1 { num: 0, s: None };
    println!("{}", s.field_count());
    let s = test2 { num: 0, s: String::new() };
    println!("{}", s.field_count());
}

这个实现的问题是它在编译时返回结构中的字段总数,所以对于 test1 和 test1,答案总是 2。我正在寻找一种方法来修改宏来计算类型为“无”的字段在结构中。

要区分None ,我们首先需要区分Option 这有点难。

有两种方法可以做到这一点:语法或通过类型系统。 从语法上讲,宏将识别具有Option<T>类型的字段(即Option < type > )。 但是,这需要 TT 咀嚼,将迫使您自己解析ty片段类型(您不能依赖它,因为您以后将无法检查其组成的令牌),并且如果用户无法使用它作为奖励将 type-alias Option

类型系统方法更健壮。 这个想法是创建一些特征,为任何类型实现,使用Option<T>将返回1如果它是Some和其他类型将始终返回1的方法。 这听起来像是一个完美的 专业化用例( 游乐场):

#![feature(min_specialization)]

pub trait OneIfSome {
    fn one_if_some(&self) -> usize;
}

impl<T> OneIfSome for T {
    default fn one_if_some(&self) -> usize { 1 }
}

impl<T> OneIfSome for Option<T> {
    fn one_if_some(&self) -> usize { self.is_some() as usize }
}

macro_rules! generate {
    ($name:ident {$($field:ident : $t:ty),+}) => {
        struct $name { $($field: $t),+ }
        
        impl $name {
            fn field_count(&self) -> usize {
                generate!(@count self $($field,)*)
            }
        }
    };
    (@count $self:ident $first_field:ident, $($rest:ident,)*) => {
        $crate::OneIfSome::one_if_some(&$self.$first_field)
            + generate!(@count $self $($rest,)*)
    };
    (@count $self:ident) => { 0 };
}

不幸的是,专业化是不稳定的,甚至没有明确的稳定之路。 我们可以用稳定的东西代替它吗?

事实证明,是的! @dtolnay 发明了一种称为“自动引用专业化”的技术,该技术完全适用于此类情况(宏内的专业化)。 我不会在这里解释它是如何工作的(链接的文章是一个很好的阅读),但这里是你如何将它应用到你的案例( 操场):

pub trait OneIfSomeGeneral {
    fn one_if_some(&self) -> usize;
}
impl<T> OneIfSomeGeneral for &'_ T {
    fn one_if_some(&self) -> usize { 1 }
}

pub trait OneIfSomeOption {
    fn one_if_some(&self) -> usize;
}
impl<T> OneIfSomeOption for Option<T> {
    fn one_if_some(&self) -> usize { self.is_some() as usize }
}

macro_rules! generate {
    ($name:ident {$($field:ident : $t:ty),+}) => {
        struct $name { $($field: $t),+ }
        
        impl $name {
            fn field_count(&self) -> usize {
                #[allow(unused_imports)]
                use $crate::{OneIfSomeGeneral, OneIfSomeOption};
                
                generate!(@count self $($field,)*)
            }
        }
    };
    (@count $self:ident $first_field:ident, $($rest:ident,)*) => {
        (&$self.$first_field).one_if_some()
            + generate!(@count $self $($rest,)*)
    };
    (@count $self:ident) => { 0 };
}

(注意:上面的代码有一个问题:如果字段类型有一个名为one_if_some()的方法,它会优先。所以选择一个明确的名称( __my_generate_macro__one_if_some() ,也许?))。

暂无
暂无

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

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