访问枚举变体的值

Access the value of an enum variant

提问人:Benjamin Philip 提问时间:5/12/2022 最后编辑:Benjamin Philip 更新时间:5/13/2022 访问量:1819

问:

我正在使用 arrayfire-rust crate 对 Arrayfire 进行一些语言绑定。

Arrayfire 有一个类型化结构 Array<T>它表示一个矩阵。所有可接受的类型都实现 HasAfEnum 特征。此特征具有许多关联类型,对于实现此特征的类型,其值不相同。

由于我需要在安全语言互操作中引用数组,因此我定义了以下结构:Rwlock

pub struct ExAfRef(pub RwLock<ExAfArray>);

impl ExAfRef {
    pub fn new(slice: &[u8], dim: Dim4, dtype: ExAfDType) -> Self {
        Self(RwLock::new(ExAfArray::new(slice, dim, dtype)))
    }

    pub fn value(&self) -> ExAfArray {
        match self.0.try_read() {
            Ok(refer) => (*refer),
            Err(_) => unreachable!(),
        }
    }
}

它包含在一个结构中:

pub struct ExAf {
    pub resource: ResourceArc<ExAfRef>,
}

impl ExAf {
    pub fn new(slice: &[u8], dim: Dim4, dtype: ExAfDType) -> Self {
        Self {
            resource: ResourceArc::new(ExAfRef::new(slice, dim, dtype)),
        }
    }

    // This function is broken
    pub fn af_value<T: HasAfEnum>(&self) -> &Array<T> {
        self.resource.value().value()
    }
}

借助以下枚举:

pub enum ExAfArray {
    U8(Array<u8>),
    S32(Array<i32>),
    S64(Array<i64>),
    F32(Array<f32>),
    F64(Array<f64>),
}

impl ExAfArray {
    pub fn new(slice: &[u8], dim: Dim4, dtype: ExAfDType) -> Self {
        let array = Array::new(slice, dim);

        match dtype {
            ExAfDType::U8 => ExAfArray::U8(array),
            ExAfDType::S32 => ExAfArray::S32(array.cast::<i32>()),
            ExAfDType::S64 => ExAfArray::S64(array.cast::<i64>()),
            ExAfDType::F32 => ExAfArray::F32(array.cast::<f32>()),
            ExAfDType::F64 => ExAfArray::F64(array.cast::<f64>()),
        }
    }

    // This function is broken
    pub fn value<T: HasAfEnum>(&self) -> &Array<T> {
        // match self {
        //     ExAfArray::U8(array) => array,
        //     ExAfArray::S32(array) => array,
        //     ExAfArray::S64(array) => array,
        //     ExAfArray::F32(array) => array,
        //     ExAfArray::F64(array) => array,
        // }

        if let ExAfArray::U8(array) = self {
            return array;
        } else if let ExAfArray::S32(array) = self {
            return array;
        } else if let ExAfArray::S64(array) = self {
            return array;
        } else if let ExAfArray::F32(array) = self {
            return array;
        } else {
            let ExAfArray::F64(array) = self;
            return array;
        }
    }

    pub fn get_type(&self) -> ExAfDType {
        match self {
            ExAfArray::U8(array) => ExAfDType::U8,
            ExAfArray::S32(array) => ExAfDType::S32,
            ExAfArray::S64(array) => ExAfDType::S64,
            ExAfArray::F32(array) => ExAfDType::F32,
            ExAfArray::F64(array) => ExAfDType::F64,
        }
    }
}

我使用了枚举,因为我的语言互操作“框架”不支持泛型结构,并且因为该特征具有关联的类型(因此动态调度使用是不可行的(至少据我所知))。HasAfEnumdyn

这对于初始化新数组很有效。

但是,当我需要对数组应用某些操作时,我需要能够访问枚举变体存储的值。但是,我无法为函数编写类型签名来访问该值,因为动态调度不可用,并且泛型太样板了。

由于所有变体都是元组,有没有办法使用内置的枚举功能访问元组变体的值?

编辑

我正在使用 rustler

Rust 枚举 arrayfire

评论

1赞 Sven Marnach 5/12/2022
“我的语言互操作框架不支持通用结构”——这似乎是问题的根源。也许解决这个问题的工作量比你问的要少。
0赞 Benjamin Philip 5/12/2022
“也许解决这个问题的工作量更少”——也许吧。我不太确定为什么不支持它,但我认为实现它可能很困难,因为这是一种没有泛型的动态类型语言。

答:

2赞 Ian S. 5/13/2022 #1

简而言之,不,没有办法做你目前在 Rust 中似乎想做的事情。

您的函数被破坏了,因为您正在尝试使用泛型,使其工作方式正交。在 Rust 中调用泛型函数时,调用者填写类型参数,而不是被调用者。但是,从某种意义上说,您的枚举“知道”具体数组类型是什么,因此只有它才能确定该类型参数应该是什么。如果这种不匹配阻碍了您的进度,这通常需要重新考虑您的代码结构。

这也解释了为什么没有内置的枚举方法可以执行您尝试执行的操作。该方法会遇到与您的方法相同的问题。当你想在 Rust 中检查一个枚举的内容时,你需要对它进行模式匹配。value

至少有一种方法可以尝试实现你的目标,但我并不真正推荐它。使代码更接近可行的一个变化是通过向函数传递一个闭包来进行修改,(下面的语法目前在 Rust 中无效,但它传达了这个想法):

pub fn modify<'a, F>(&'a self, op: F)
where
    F: for<T: HasAfEnum> FnOnce(&'a Array<T>)
{
    // This looks repetitive, but the idea is that in each branch
    // the type parameter T takes on the appropriate type for the variant
    match self {
        ExAfArray::U8(array) => op(array),
        ExAfArray::S32(array) => op(array),
        ExAfArray::S64(array) => op(array),
        ExAfArray::F32(array) => op(array),
        ExAfArray::F64(array) => op(array),
    }
}

不幸的是,语法尚不存在,我什至不确定是否有添加它的建议。这可以通过宏来解决:for<T> FnTrait(T)

pub(crate) fn call_unary<F, T, U>(arg: T, f: F) -> U
where F: FnOnce(T) -> U {
    f(arg)
}

macro_rules! modify {
    ($ex_af_array:expr, $op:expr) => {
        match &$ex_af_array {
            ExAfArray::U8(array) => call_unary(array, $op),
            ExAfArray::S32(array) => call_unary(array, $op),
            ExAfArray::S64(array) => call_unary(array, $op),
            ExAfArray::F32(array) => call_unary(array, $op),
            ExAfArray::F64(array) => call_unary(array, $op),
        }
    };
}

需要帮助程序来确保类型推断正常工作。 当需要推断参数的类型时,将无法编译。call_unary($op)(array)$op

现在,此解决方案主要涵盖了将提供的功能,但它不是非常干净的代码(尤其是在宏体被清理之后),如果宏被滥用,编译器错误将很差。for<T> FnTrait(T)

评论

0赞 rodrigo 5/13/2022
F: for<T: HasAfEnum> FnOnce(&'a Array<T>):导致错误:在此上下文中只能使用生存期参数:-/。
0赞 Ian S. 5/13/2022
@rodrigo 这就是为什么我在回答中说“下面的语法目前不是有效的 Rust,但它传达了这个想法”
0赞 rodrigo 5/13/2022
啊,是的。我看到了我不知道的代码,然后匆匆忙忙地去测试它,没有阅读散文。