How to return concrete type from generic function?

天大地大妈咪最大 提交于 2021-02-16 15:29:06

问题


In the example below the Default trait is used just for demonstration purposes.

My questions are:

  1. What is the difference between the declarations of f() and g()?
  2. Why g() doesn't compile since it's identical to f()?
  3. How can I return a concrete type out of a impl trait generically typed declaration?
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{}
}

回答1:


What is the difference between the declarations of f() and g()?

  • f returns some type which implements Default. The caller of f has no say in what type it will return.
  • g returns some type which implements Default. The caller of g gets to pick the exact type that must be returned.

You can clearly see this difference in how f and g can be called. For example:

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()
}

Why g() doesn't compile since it's identical to f()?

They're not identical. The implementation for f compiles because it can only be called in 1 way and it will always return the exact same type. The implementation for g fails to compile because it can get called infinitely many ways for all different types but it will always return Something which is broken.

How can I return a concrete type out of a impl trait generically typed declaration?

If I'm understanding your question correctly, you can't. When you use generics you let the caller decide the types your function must use, so your function's implementation itself must be generic. If you want to construct and return a generic type within a generic function the usual way to go about that is to put a Default trait bound on the generic type and use that within your implementation:

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

If you need to conditionally select the concrete type within the function then the only other solution is to return a trait object:

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)
    }
}



回答2:


how can I return a concrete type out of a "impl trait" generically typed declaration?

By "impl trait" generically typed declaration I presume you mean "impl trait" rewritten to use named generics. However, that's a false premise - impl Trait in return position was introduced precisely because you can't express it using named generics. To see this, consider first impl Trait in argument position, such as this function:

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

You can rewrite that function to use named generics as follows:

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

Barring minor technical differences, the two are equivalent. However, if impl Trait is in return position, such as here:

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

...you cannot rewrite it to use generics without losing generality. For example, this won't compile:

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

...because, as explained by pretzelhammer, the signature promises the caller the ability to choose which type to return (out of those that implement Iterator<Item = u32>), but the implementation only ever returns a concrete type, <Vec<u32> as IntoIterator>::IntoIter.

On the other hand, this does compile:

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

...but now the generality is lost because foo() must be implemented as a combination of Vec and into_iter() - even adding a map() in between the two would break it.

This also compiles:

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

...but at the cost of allocating the iterator on the heap and disabling some optimizations.



来源:https://stackoverflow.com/questions/66207126/how-to-return-concrete-type-from-generic-function

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!