问题
In the example below the Default
trait is used just for demonstration purposes.
My questions are:
- What is the difference between the declarations of
f()
andg()
? - Why
g()
doesn't compile since it's identical tof()
? - 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()
andg()
?
f
returns some type which implementsDefault
. The caller off
has no say in what type it will return.g
returns some type which implementsDefault
. The caller ofg
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 tof()
?
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