【问题标题】:Is it possible to calculate the return type of a Rust function or trait method based on its arguments?是否可以根据参数计算 Rust 函数或 trait 方法的返回类型?
【发布时间】:2017-08-24 01:45:42
【问题描述】:

我可以在 Rust 中实现类似于 boost::math::tools::promote_args 的东西吗?另见Idiomatic C++11 type promotion

更具体地说:是否可以根据参数计算函数或特征方法的返回类型并确保返回类型与参数之一具有相同的类型?

考虑以下情况。我有两个结构:

#[derive(Debug, Clone, Copy)]
struct MySimpleType(f64);

#[derive(Debug, Clone, Copy)]
struct MyComplexType(f64, f64);

其中MySimpleType 可以通过From trait 提升为MyComplexType

impl From<MySimpleType> for MyComplexType {
    fn from(src: MySimpleType) -> MyComplexType {
        let MySimpleType(x1) = src;
        MyComplexType(x1, 0.0)
    }
 }

我想编写一个函数,它接受两个MySimpleTypeMyComplexType 类型的参数,如果所有参数都输入为MySimpleType,则返回一个MySimpleType 类型的值,否则该函数应该返回一个类型的值MyComplexType。假设我已经为这两种类型实现了Add&lt;Output=Self&gt;,我可以这样做:

trait Foo<S, T> {
    fn foo(s: S, t: T) -> Self;
}

impl<S, T, O> Foo<S, T> for O
    where O: From<S> + From<T> + Add<Output = Self>
{
    fn foo(s: S, t: T) -> Self {
        let s: O = From::from(s);
        let t: O = From::from(t);
        s + t
    }
}

但是编译器不知道O 应该是ST,我必须注释大多数方法调用。

我的第二次尝试是使用稍微不同的 trait 并编写两个实现:

trait Foo<S, T> {
    fn foo(s: S, t: T) -> Self;
}

impl Foo<MySimpleType, MySimpleType> for MySimpleType {
    fn foo(s: MySimpleType, t: MySimpleType) -> Self {
        s + t
    }
}

impl<S, T> Foo<S, T> for MyComplexType
    where MyComplexType: From<S> + From<T>
{
    fn foo(s: S, t: T) -> Self {
        let s: MyComplexType = From::from(s);
        let t: MyComplexType = From::from(t);
        s + t
    }
}

但同样,编译器无法计算出的返回类型

Foo::foo(MySimpleType(1.0), MySimpleType(1.0))

第三次尝试类似于std::ops::{Add, Mul, ...}。使用关联类型并为每种可能的参数类型组合编写特定的实现

trait Foo<T> {
    type Output;
    fn foo(self, t: T) -> Self::Output;
}

impl<T: Add<Output=T>> Foo<T> for T {
    type Output = Self;
    fn foo(self, t: T) -> Self::Output {
        self + t
    }
}

impl Foo<MySimpleType> for MyComplexType {
    type Output = Self;
    fn foo(self, t: MySimpleType) -> Self::Output {
        let t: Self = From::from(t);
        self + t
    }
}

impl Foo<MyComplexType> for MySimpleType {
    type Output = MyComplexType;
    fn foo(self, t: MyComplexType) -> Self::Output {
        let s: MyComplexType = From::from(self);
        s + t
    }
}

这似乎是最好的解决方案,直到需要一个带有n 参数的函数。因为那时必须编写2^n - n + 1 impl 语句。当然,如果考虑超过两种类型,情况会变得更糟。

===

编辑:

在我的代码中,我有多个嵌套函数调用,我想避免不必要的类型提升,因为简单类型的函数评估对于复杂类型来说既便宜又昂贵。通过使用@MatthieuM。的建议的解决方案,这是没有实现的。请考虑以下示例

#![feature(core_intrinsics)]

use std::ops::Add;

trait Promote<Target> {
    fn promote(self) -> Target;
}

impl<T> Promote<T> for T {
    fn promote(self) -> T {
        self
    }
}

impl Promote<u64> for u32 {
    fn promote(self) -> u64 {
        self as u64
    }
}

fn foo<Result, Left, Right>(left: Left, right: Right) -> Result
    where Left: Promote<Result>,
        Right: Promote<Result>,
        Result: Add<Output = Result>
{
    println!("============\nFoo called");
    println!("Left: {}", unsafe { std::intrinsics::type_name::<Left>() });
    println!("Right: {}",
            unsafe { std::intrinsics::type_name::<Right>() });
    println!("Result: {}",
            unsafe { std::intrinsics::type_name::<Result>() });
    left.promote() + right.promote()
}

fn bar<Result, Left, Right>(left: Left, right: Right) -> Result
    where Left: Promote<Result>,
        Right: Promote<Result>,
        Result: Add<Output = Result>
{
    left.promote() + right.promote()
}

fn baz<Result, A, B, C, D>(a: A, b: B, c: C, d: D) -> Result
    where A: Promote<Result>,
        B: Promote<Result>,
        C: Promote<Result>,
        D: Promote<Result>,
        Result: Add<Output = Result>
{
    let lhs = foo(a, b).promote();
    let rhs = bar(c, d).promote();
    lhs + rhs
}

fn main() {
    let one = baz(1u32, 1u32, 1u64, 1u32);
    println!("{}", one);
}

【问题讨论】:

  • “直到需要一个带有 n 个参数的函数” – 你是否考虑过编写一个宏来按需生成必要的特征?
  • 我考虑过使用宏,但我很好奇是否存在其他解决方案。

标签: arguments rust type-promotion


【解决方案1】:

我希望实现提升的最简单方法是创建 Promote 特征:

trait Promote<Target> {
    fn promote(self) -> Target;
}

impl<T> Promote<T> for T {
    fn promote(self) -> T { self }
}

注意:我提供了一个全面的实现,因为所有类型都可以提升为自己。

这里不能选择使用关联类型,因为可以将单个类型提升为多种类型;因此我们只使用常规类型参数。


使用这个,一个简单的例子是:

impl Promote<u64> for u32 {
    fn promote(self) -> u64 { self as u64 }
}

fn add<Result, Left, Right>(left: Left, right: Right) -> Result
    where
        Left: Promote<Result>,
        Right: Promote<Result>,
        Result: Add<Output = Result>
{
    left.promote() + right.promote()
}

fn main() {
    let one: u32 = add(1u32, 1u32);
    let two: u64 = add(1u32, 2u64);
    let three: u64 = add(2u64, 1u32);
    let four: u64 = add(2u64, 2u64);
    println!("{} {} {} {}", one, two, three, four);
}

唯一的问题是,在有两个 u32 参数的情况下,必须指定结果类型,否则编译器无法选择使用哪个可能的 Promote 实现:Promote&lt;u32&gt;Promote&lt;u64&gt;

我不确定这在实践中是否是一个问题,因为在某些时候你应该有一个具体的类型来锚定类型推断。例如:

fn main() {
    let v = vec![add(1u32, 1u32), add(1u32, 2u64)];
    println!("{:?}", v);
}

编译时没有类型提示,因为add(1u32, 2u64) 只能是u64,因此由于Vec 是同构集合,add(1u32, 1u32) 必须在此处返回u64


不过,正如您所经历的,有时您需要能够将结果导向超出类型推断所能处理的范围。没关系,你只需要它的另一个特征:

trait PromoteTarget {
    type Output;
}

impl<T> PromoteTarget for (T, T) {
    type Output = T;
}

然后是一个小实现:

impl PromoteTarget for (u32, u64) {
    type Output = u64;
}

impl PromoteTarget for (u64, u32) {
    type Output = u64;
}

这样,我们可以重写baz 签名以正确解释所有中间类型。不幸的是,我不知道有什么方法可以在 where 子句中引入别名,所以请做好准备:

fn baz<Result, A, B, C, D>(a: A, b: B, c: C, d: D) -> Result
    where
        A: Promote<<(A, B) as PromoteTarget>::Output>,
        B: Promote<<(A, B) as PromoteTarget>::Output>,
        C: Promote<<(C, D) as PromoteTarget>::Output>,
        D: Promote<<(C, D) as PromoteTarget>::Output>,
        (A, B): PromoteTarget,
        (C, D): PromoteTarget,
        <(A, B) as PromoteTarget>::Output: Promote<Result> + Add<Output = <(A, B) as PromoteTarget>::Output>,
        <(C, D) as PromoteTarget>::Output: Promote<Result> + Add<Output = <(C, D) as PromoteTarget>::Output>,
        Result: Add<Output = Result>
{
    let lhs = foo(a, b).promote();
    let rhs = bar(c, d).promote();
    lhs + rhs
}

链接到操场here,所以你可以查看结果:

============
Foo called
Left: u32
Right: u32
Result: u32
4

【讨论】:

  • et tu,马修?您是否已将 where 子句切换到块式缩进?
  • PromoteFrom有什么区别?
  • @Shepmaster:(1)我正在尝试习惯这种块式缩进的东西,(2)我使用Promote来演示这个概念,因为它更容易破解;它开始有点不同,老实说,我没有意识到它现在匹配Into。我也不确定这正是 OP 想要的......或者类型推断不会是不够的。
  • 还有一个好处是,this crate 可以为标准库类型定义提升,尽管我猜From 是为了扩大整数转换而实现的(总是这样吗?)
  • @Shepmaster:我想From 的主要恼人之处在于提升为usize:它仅针对u8 实现,以防您在8 位架构上摆弄。因此,确实,使用自定义特征,您有机会转换为 usize 以获得更多类型......但这确实是一个附带属性:)
猜你喜欢
  • 2021-02-01
  • 2018-04-30
  • 1970-01-01
  • 2020-03-05
  • 2014-12-22
  • 1970-01-01
  • 2021-12-15
  • 1970-01-01
  • 2016-08-10
相关资源
最近更新 更多