从函数中返回的泛型类型不同于输入类型

3
我正在尝试在Rust中创建一个解析器,读取我们模拟的输出文件,找到正确的块,返回该块的内容。根据给定的维度确定它可以是单个int或int或float向量。
但是,我找不到一种使用通用类型T的方法来使相同的函数适用于i32和f64数字。
我不知道如何以这种方式使用泛型。
我今天刚开始学习Rust,请像我五岁的孩子一样向我解释解决方案。
我的理解问题的简化版本如下:
fn parse_b2f<T>(dims: Vec<i32>) -> Result<Vec<T>, String> {
    let nentries = dims.iter().fold(1, |res, a| res*a);
    Ok(vec![nentries])
}

fn main() {
    let dims = vec![3720, 9];
    let data = parse_b2f::<i32>(dims);
}

但我得到了“预期类型参数T,找到i32”的错误提示,在parse_b2f函数的最后一行指向nentries。如果我在main函数中提供了i32作为函数调用的参数,为什么T仍然被视为通用类型而不是i32呢? 如果我将T更改为i32,则函数可以正常工作,但想法是让用户传递类型(对于索引数组为i32,对于实验数据数组为f64等),而不是编写两个完全相同逻辑的函数。
我本来希望能够做类似于vec![nentries as T]的事情,因为使用i32和f64单独运行很好,但在T上不起作用。
我也考虑过使用枚举类型:
enum Numbers {
I32(i32),
F64(f64),
}

使用 T 而非其他替代品

fn parse_b2f<Numbers>(dims: Vec<i32>) -> Result<Vec<Numbers>, String> {...}

let data = parse_b2f::Numbers::I32(dims);

但是我不太清楚在这种情况下如何使用该枚举。

2个回答

4

这在 C++ 中可以工作,因为类型直到实例化时才进行检查。在 Rust 中,泛型函数必须独立存在。 parse_b2f 函数不限制 T,因此它必须对于任何类型 T 都可调用。这就是为什么会出现错误的原因。您可以删除 main() 的整个主体,但仍将收到此错误。

您需要做的就是适当地约束 T

在这个简单的例子中,您需要说“T 必须是支持乘法的类型,其中结果是 T。” 您可以通过在 std::ops::Mul<Output = T> 上进行约束来实现这一点。

此外,您需要一种方法来获取 1 的初始值,可以使用辅助 trait 来实现这一点。(在 num-traits crate 中有 这样一个 trait。)

最后,你需要将T限制在Copy上,或者使用.into_iter()代替.iter(),因为.iter()会产生引用(并且被引用的对象必须被复制以供乘法操作使用),但是.into_iter()通过消耗输入向量来产生值。

这里有一个可行的例子:

use std::ops::Mul;

use num_traits::One;

fn parse_b2f<T: One + Mul<Output = T>>(dims: Vec<T>) -> Result<Vec<T>, String> {
    let nentries = dims.into_iter().fold(T::one(), |res, a| res*a);
    Ok(vec![nentries])
}

考虑将函数更改为接受任何可以转换为TIterator(我们称其为 IntoIterator<Item = T>)。这样可以传递一个Vec<T>,也可以传递一个非拥有的迭代器:
use std::ops::Mul;

use num_traits::One;

fn parse_b2f<T: One + Mul<Output=T>>(dims: impl IntoIterator<Item = T>)
    -> Result<Vec<T>, String>
{
    let nentries = dims.into_iter().fold(T::one(), |res, a| res*a);
    Ok(vec![nentries])
}

非常感谢您。我已经尝试了您提到的解决方案的某些部分,但我希望有更简单的方法来实现它(正如您提到的在C++中)。我将尝试使用您的概念实现整个过程(这只是一个小例子,在现实中T将会影响数据数组的元素等)。 - Iván Paradela Pérez
@IvánParadelaPérez 你仍然可以让它工作,我相信。std::opsnum-traits包中有很多特性可能会有用,而且你总是可以创建自己的特性并在数字类型上实现它们。 - cdhowie

2
@cdhowie的回答解释得很好,但是与其使用它们展示的特性(还包括一个外部crate),你可以意识到你正在做与 Iterator::product() 相同的事情,只需使用它的trait-Product。你可以使用 T: Product<T>into_iter() 或者 T: Product<&T>iter()
fn parse_b2f<T: std::iter::Product<T>>(dims: Vec<T>) -> Result<Vec<T>, String> {
    let nentries = dims.into_iter().product();
    Ok(vec![nentries])
}

// Or

fn parse_b2f<T: for<'a> std::iter::Product<&'a T>>(dims: Vec<T>) -> Result<Vec<T>, String> {
    let nentries = dims.iter().product();
    Ok(vec![nentries])
}

这是一个很棒的补充,我之前不知道这个产品。我也喜欢你最后一行的解释。我的原始问题有所不同,但我认为我过于简化了问题,导致答案给出了dims和结果相同类型T,但我想通过这个答案,我将设法让代码工作,现在你们给了我一些想法。 - Iván Paradela Pérez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接