不同的返回值取决于通用常量的值

Lat*_*ker 3 generics rust

让我们考虑这样一个函数:

fn test<const N: usize>() -> [f64; N] {
    if N == 1 {
        [0.0_f64; 1]
    } else if N == 2 {
        [1.0_f64; 2]
    } else {
        panic!()
    }
}
Run Code Online (Sandbox Code Playgroud)

N我的理解是编译器会在编译时评估 的值。如果是这种情况,该if语句也可以在编译时求值,因此应该返回正确的类型,因为[0.0_f64; 1]仅返回 ifN == 1[1.0_f64; 2]仅返回 if N == 2

现在,当我尝试编译此代码时,编译器失败,基本上告诉我返回数组的尺寸是错误的,因为它们没有明确地具有N长度。

我确实意识到,我可以将这个具体示例实现为

fn test<const N: usize>() -> [f64; N] {
    match N {
        1 => { [0.0_f64; N] },
        2 => { [1.0_f64; N] },
        _ => { panic!("Invalid value {}", N) },
    }
}
Run Code Online (Sandbox Code Playgroud)

但这在我的实际代码中不起作用,因为它针对不同的分支使用具有固定数组大小的不同函数。

有办法做到这一点吗?也许使用像#![cfg]万客罗这样的东西?

为了澄清为什么我的问题不起作用,让我们写下来:

fn some_fct() -> [f64; 1] {
    [0.0_f64; 1]
}
fn some_other_fct() -> [f64; 2] {
    [1.0_f64; 2]
}

fn test<const N: usize>() -> [f64; N] {
    match N {
        1 => some_fct(),
        2 => some_other_fct(),
        _ => {
            panic!("Invalid value {}", N)
        }
    }
}

Run Code Online (Sandbox Code Playgroud)

由于程序结构中的其他限制,我无法真正以通用大小编写some_fct()和返回。some_other_fct()

rod*_*igo 8

您可以使用通用特征来做到这一点:

trait Test<const N: usize> {
    fn test() -> [f64; N];
}
Run Code Online (Sandbox Code Playgroud)

然后将其实现为零大小的类型:

struct T;

impl Test<1> for T {
    fn test() -> [f64; 1] {
        return [0.0_f64; 1];
    }
}

impl Test<2> for T {
    fn test() -> [f64; 2] {
        return [1.0_f64; 2];
    }
}
Run Code Online (Sandbox Code Playgroud)

缺点是调用起来有点麻烦:

fn main() {
    dbg!(<T as Test<1>>::test());
    dbg!(<T as Test<2>>::test());
}
Run Code Online (Sandbox Code Playgroud)

但正如@eggyal 下面评论的那样,您可以添加一个具有编写良好的绑定的通用函数来获取所需的语法:

fn test<const N: usize>() -> [f64; N]
where
    T: Test<N>
{
    T::test()
}
fn main() {
    dbg!(test::<1>());
    dbg!(test::<2>());
}
Run Code Online (Sandbox Code Playgroud)

现在,你就没有了“用panic!N了”的行为。考虑这是一个功能而不是限制:如果您使用错误,N您的代码将无法编译,而不是在运行时出现恐慌。

如果你真的想要这种panic!()行为,你可以使用 的不稳定功能来获得它#![feature(specialization)],只需添加default到这个实现中:

impl<const N: usize> Test<N> for T {
    default fn test() -> [f64; N] {
        panic!();
    }
}
Run Code Online (Sandbox Code Playgroud)

但该功能被明确标记为不完整,所以我还不会指望它。


use*_*342 5

这是一个不是特别聪明的解决方案,但很容易理解并且类似于原始解决方案:

fn test<const N: usize>() -> [f64; N] {
    match N {
        1 => some_fct().as_slice().try_into().unwrap(),
        2 => some_other_fct().as_slice().try_into().unwrap(),
        _ => {
            panic!("Invalid value {}", N)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

尽管代码看起来像是在运行时检查数组大小,但godbolt表明 rustc/LLVM 能够推理出[f64; N].as_slice().try_into()始终成功地将数组转换的切片强制转换为[f64; N]. 生成的代码test<1>因此test<2>不包含任何检查或恐慌,并且test<N>由于N>2全能匹配臂中的恐慌而无条件地发生恐慌。