【问题标题】:How to return concrete type from generic function?如何从泛型函数返回具体类型?
【发布时间】:2021-05-18 06:52:30
【问题描述】:

在下面的示例中,Default trait 仅用于演示目的。

我的问题是:

  1. f()g()的声明有什么区别?
  2. 为什么g()f() 相同,所以无法编译?
  3. 如何从impl trait 泛型类型声明中返回具体类型?
struct Something {
}

impl Default for Something {
    fn default() -> Self {
        Something{}
    }
}

// This compiles.
pub fn f() -> impl Default {
    Something{}
}

// This doesn't.
pub fn g<T: Default>() -> T {
    Something{}
}

【问题讨论】:

  • g 返回 T 但您尝试返回 Something
  • 是的,但是Something 实现了T。而且T 不是类型,而是类型约束/特征。那么?
  • “某物实现 T”是什么意思?如果你做g::&lt;i32&gt;(),那么g必须返回i32,而Something不是i32
  • g 将返回在编译时填充的具体类型。然而,f 不提供任何保证(例如返回数据的大小或类型),除了它将实现Default
  • 好的,我想我现在明白了,非常感谢!你们俩! :)

标签: function generics types rust traits


【解决方案1】:

f()g()的声明有什么区别?

  • f 返回一些实现Default 的类型。 f 的调用者没有说它会返回什么类型
  • g 返回一些实现Default 的类型。 g 的调用者可以选择必须返回的确切类型

您可以清楚地看到fg 调用方式的区别。例如:

fn main() {
    let t = f(); // this is the only way to call f()
    let t = g::<i32>(); // I can call g() like this
    let t = g::<String>(); // or like this
    let t = g::<Vec<Box<u8>>(); // or like this... and so on!
    // there's potentially infinitely many ways I can call g()
    // and yet there is only 1 way I can call f()
}

为什么g()f() 相同,所以无法编译?

它们并不相同。 f 的实现可以编译,因为它只能以一种方式调用,并且它总是返回完全相同的类型。 g 的实现无法编译,因为它可以被所有不同类型的无限多种方式调用,但它总是会返回已损坏的 Something

如何从impl trait 泛型类型声明中返回具体类型?

如果我正确理解了您的问题,您就不能。当你使用泛型时,你让调用者决定你的函数必须使用的类型,所以你的函数的实现本身必须是泛型的。如果你想在泛型函数中构造并返回一个泛型类型,通常的方法是在泛型类型上绑定一个Default trait 并在你的实现中使用它:

// now works!
fn g<T: Default>() -> T {
    T::default()
}

如果您需要在函数中有条件地选择具体类型,那么唯一的其他解决方案是返回一个特征对象:

struct Something;
struct SomethingElse;

trait Trait {}

impl Trait for Something {}
impl Trait for SomethingElse {}

fn g(some_condition: bool) -> Box<dyn Trait> {
    if some_condition {
        Box::new(Something)
    } else {
        Box::new(SomethingElse)
    }
}

【讨论】:

  • "f 返回一些实现 Default 的类型。f 的调用者对于它将返回什么类型没有发言权。"它不仅没有发言权,它也不知道,它唯一知道的是它实现了Default(对于这个特定的特征完全没用)。
【解决方案2】:

如何从“impl trait”泛型类型声明中返回具体类型?

“impl trait”泛型类型声明我想你的意思是“impl trait”重写为使用命名泛型。然而,这是一个错误的前提——impl Trait 在返回位置被引入正是因为你不能使用命名泛型来表达它。要看到这一点,请首先考虑参数位置中的impl Trait,例如这个函数:

fn foo(iter: impl Iterator<Item = u32>) -> usize {
    iter.count()
}

您可以重写该函数以使用命名泛型,如下所示:

fn foo<I: Iterator<Item = u32>>(iter: I) -> usize {
    iter.count()
}

除非存在细微的技术差异,否则两者是等价的。但是,如果impl Trait 在返回位置,比如这里:

fn foo() -> impl Iterator<Item = u32> {
    vec![1, 2, 3].into_iter()
}

...你不能在不失一般性的情况下重写它来使用泛型。例如,这不会编译:

fn foo<T: Iterator<Item = u32>>() -> T {
    vec![1, 2, 3].into_iter()
}

...因为,正如 pretzelhammer 所解释的,签名向调用者保证了选择返回哪种类型的能力(在那些实现 Iterator&lt;Item = u32&gt; 的类型中),但实现只返回一个具体类型,&lt;Vec&lt;u32&gt; as IntoIterator&gt;::IntoIter .

另一方面,这确实可以编译:

fn foo() -> <Vec<u32> as IntoIterator>::IntoIter {
    vec![1, 2, 3].into_iter()
}

...但现在失去了一般性,因为 foo() 必须 实现为 Vecinto_iter() 的组合 - 即使在两者之间添加 map() 也会中断它。

这也可以编译:

fn foo() -> Box<dyn Iterator<Item = u32>> {
    Box::new(vec![1, 2, 3].into_iter())
}

...但代价是在堆上分配迭代器并禁用一些优化。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-11-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多