Using impl Trait in a recursive function - rust

I've been experimenting with impl Trait and I came across this error when building a recursive function:
error[E0308]: if and else have incompatible types
--> src/main.rs:16:5
|
16 | / if logic {
17 | | one(false)
18 | | } else {
19 | | two()
20 | | }
| |_____^ expected opaque type, found a different opaque type
|
= note: expected type `impl Meow` (opaque type)
found type `impl Meow` (opaque type)
Here's the code to reproduce (Rust playground link):
trait Meow {
fn meow();
}
struct Cat(u64);
impl Meow for Cat {
fn meow() {}
}
fn one(gate: bool) -> impl Meow {
if gate {
one(false)
} else {
two()
}
}
fn two() -> impl Meow {
Cat(42)
}
fn main() {
let _ = one(true);
}
I haven't been able to find documentation about this particular issue and I find it odd that the compiler returns an error that roughly says "these two identical things are different".
Is there a way I can support the impl Trait syntax whilst doing this kind of recusion, please?

Disclaimer: this answer assumes that the reader understands that -> impl Trait requires a single type to be returned; see this question for returning different types.
Opacity
One of the core principles of Rust is that type-checking is entirely driven by the interface of functions, types, etc... and the implementation is ignored.
With regard to -> impl Trait functionality, this manifests by the language treating each -> impl Trait as an opaque type, solely identified by the function it comes from.
As a result, you can call the same function twice:
use std::fmt::Debug;
fn cat(name: &str) -> impl Debug { format!("Meow {}", name) }
fn meow(g: bool) -> impl Debug {
if g {
cat("Mario")
} else {
cat("Luigi")
}
}
fn main() {
println!("{:?}", meow(true));
}
But you cannot call different functions, even when they return the same type, if at least one is hidden behind -> impl Trait:
use std::fmt::Debug;
fn mario() -> impl Debug { "Meow Mario" }
fn luigi() -> &'static str { "Meow Luigi" }
fn meow(g: bool) -> impl Debug {
if g {
mario()
} else {
luigi()
}
}
fn main() {
println!("{:?}", meow(true));
}
Yields:
error[E0308]: if and else have incompatible types
--> src/main.rs:8:9
|
8 | / if g {
9 | | mario()
10 | | } else {
11 | | luigi()
12 | | }
| |_________^ expected opaque type, found &str
|
= note: expected type `impl std::fmt::Debug`
found type `&str`
And with two hidden behind -> impl Trait:
use std::fmt::Debug;
fn mario() -> impl Debug { "Meow Mario" }
fn luigi() -> impl Debug { "Meow Luigi" }
fn meow(g: bool) -> impl Debug {
if g {
mario()
} else {
luigi()
}
}
fn main() {
println!("{:?}", meow(true));
}
Yields the same error message than you got:
error[E0308]: if and else have incompatible types
--> src/main.rs:8:5
|
8 | / if g {
9 | | mario()
10 | | } else {
11 | | luigi()
12 | | }
| |_____^ expected opaque type, found a different opaque type
|
= note: expected type `impl std::fmt::Debug` (opaque type)
found type `impl std::fmt::Debug` (opaque type)
Interaction with Recursion
None.
The language does not special-case recursion here, and therefore does not realize that, in the case presented in the question, there is only ever one type involved. Instead, it notices fn one(...) -> impl Meow and fn two(...) -> impl Meow and concludes that those are different opaque types and therefore compile-time unification is impossible.
It may be reasonable to submit a RFC to tweak this aspect, either by arguing on the point of view of recursion, or by arguing on the point of view of module-level visibility; this is beyond the scope of this answer.
Work around
The only possibility is to ensure that the type is unique, and this requires naming it. Once you have captured the type in a name, you can consistently apply it everywhere it needs to match.
I'll refer you to #Anders' answer for his clever work-around.

I think an ideal compiler would accept your code, but the current language doesn’t allow for the recursive reasoning that would be needed to figure out that the types are actually the same in this case. You can work around this missing feature by abstracting over the impl Meow type with a type variable:
fn one_template<T: Meow>(gate: bool, two: impl FnOnce() -> T) -> T {
if gate {
one_template(false, two)
} else {
two()
}
}
fn one(gate: bool) -> impl Meow {
one_template(gate, two)
}
Rust playground link

Related

How to return an iterator for single or multiple values in enum

Consider the following code:
struct MediaKind;
struct OtherFields;
enum Media {
Single(MediaKind),
Group(Vec<MediaKind>, OtherFields),
}
impl Media {
fn iter(&self) -> impl Iterator<Item = &MediaKind> {
match self {
Self::Single(media) => [media].iter(),
Self::Group(media_vec, _) => media_vec.iter(),
}
}
}
Compile error:
error[E0308]: `match` arms have incompatible types
--> src/lib.rs:13:42
|
11 | / match self {
12 | | Self::Single(media) => [media].iter(),
| | -------------- this is found to be of type `std::slice::Iter<'_, &MediaKind>`
13 | | Self::Group(media_vec, _) => media_vec.iter(),
| | ^^^^^^^^^^^^^^^^ expected `&MediaKind`, found struct `MediaKind`
14 | | }
| |_________- `match` arms have incompatible types
|
= note: expected struct `std::slice::Iter<'_, &MediaKind>`
found struct `std::slice::Iter<'_, MediaKind>`
Because the Group arm may contain additional fields, replacing this entire enum with a Vec is not a preferred solution.
I have tried either crate to solve the problem in the following way:
fn iter(&self) -> impl Iterator<Item = &MediaKind> {
use either::*;
match self {
Self::Single(media) => Left([media].into_iter()),
Self::Group(media_vec, _) => Right(media_vec.iter()),
}
}
But I'm wondering if it's possible to fix it without depending on an external crate.
> Link to playground
In this case, because one case is a slice iterator and the other is one element, you can use a trick: std::slice::from_ref():
fn iter(&self) -> impl Iterator<Item = &MediaKind> {
match self {
Self::Single(media) => std::slice::from_ref(media).iter(),
Self::Group(media_vec, _) => media_vec.iter(),
}
}
Playground.

Convert vector of type A to type B where A is convertible to B

I'm learning rust, and one of the most basic things I want to do is to take one vector of homogenous type A which is convertible to another type B (since From<> is implemented and thus we can use .into()). When I tried running the below I got the following:
struct A {
x: String
}
struct B {
x: String
}
impl From<A> for B {
fn from(a: A) -> Self {
B { x: a.x }
}
}
impl B {
pub fn from_many<T: Into<B> + Clone>(v: Vec<T>) -> Self {
B { x: v.iter().map(|e| B::from(e.clone()).x).collect::<Vec<String>>().join(" ") }
}
}
fn main() {
...
}
I got:
error[E0277]: the trait bound `B: From<T>` is not satisfied
--> src/main.rs:17:41
|
17 | B { x: v.iter().map(|e| B::from(e.clone()).x).collect::<Vec<String>>().join(" ") }
| ------- ^^^^^^^^^ the trait `From<T>` is not implemented for `B`
| |
| required by a bound introduced by this call
|
help: consider introducing a `where` clause, but there might be an alternative better way to express this requirement
|
15 | impl B where B: From<T> {
| ++++++++++++++++
I originally tried it without the clone(), but thought that it didn't accept references:
...
impl B {
pub fn from_many<T: Into<B>>(v: Vec<T>) -> Self {
B { x: v.iter().map(|e| B::from(e).x).collect::<Vec<String>>().join(" ") }
}
}
...
which yielded:
error[E0277]: the trait bound `B: From<&T>` is not satisfied
--> src/main.rs:17:41
|
17 | B { x: v.iter().map(|e| B::from(e).x).collect::<Vec<String>>().join(" ") }
| ------- ^ the trait `From<&T>` is not implemented for `B`
| |
| required by a bound introduced by this call
|
= help: the trait `From<A>` is implemented for `B`
I'm not asking for an arbitrary T here, I'm asking for T which has Into<B> for T defined (in this case, I believe it's defined since I defined the From trait). Did I do something stupid here?
You're missing a very simple but easy to miss fact: if you have a From<T> for U implementation you automatically have a Into<U> for T implementation, but the opposite is not true. Thus, if by generics you require T: Into<B> (which is the right thing to do since it is more generic than B: From<T>), you need to use .into() and not B::from():
impl B {
pub fn from_many<T: Into<B> + Clone>(v: Vec<T>) -> Self {
B { x: v.iter().map(|e| e.clone().into().x).collect::<Vec<String>>().join(" ") }
}
}
Another, but unrelated, thing you're missing is that since you have an owned Vec<T> you can use into_iter() and then you don't need to .clone():
impl B {
pub fn from_many<T: Into<B>>(v: Vec<T>) -> Self {
B { x: v.into_iter().map(|e| e.into().x).collect::<Vec<String>>().join(" ") }
}
}

Documentation regarding using traits for associated types

I'm unclear as to why traits are usable within the RHS of the type syntax for what I recognize as an associated type. I searched through the language reference and could not find it, but am not sure if I did not find it due to lack of familiarity/understanding, or because it's as of yet undocumented.
Here is a playground with the same code.
trait ATrait {
fn a(&self) -> f64;
}
trait BTrait {
fn b(&self) -> f64;
}
trait Container {
// Why can we just use 'traits'?
type Item: ATrait + BTrait;
fn describe_container() -> i32;
}
struct ContainerType;
impl Container for ContainerType {
type Item = ItemType;
fn describe_container() -> i32 {
42
}
}
struct ItemType;
impl ATrait for ItemType {
fn a(&self) -> f64 {
3.141
}
}
impl BTrait for ItemType {
fn b(&self) -> f64 {
2.718
}
}
fn main() {
let _ct = ContainerType {};
ContainerType::describe_container();
}
It appears in the reference here. Specifically, AssociatedItem refers to TypeAlias, and it includes ( : TypeParamBounds )? and mentions (at the end of the page) that:
A type alias with TypeParamBounds may only specified when used as an associated type in a trait.
These bounds mean that for every type that implement the trait, the type provided for this associated type will have to implement the mentioned traits. In your example, ItemType, specified as Container::Item for ContainerType, must implement ATrait and BTrait, or errors will be emitted:
error[E0277]: the trait bound `ItemType: BTrait` is not satisfied
--> src/main.rs:17:17
|
17 | type Item = ItemType;
| ^^^^^^^^ the trait `BTrait` is not implemented for `ItemType`
|
note: required by a bound in `Container::Item`
--> src/main.rs:11:25
|
11 | type Item: ATrait + BTrait;
| ^^^^^^ required by this bound in `Container::Item`
error[E0277]: the trait bound `ItemType: ATrait` is not satisfied
--> src/main.rs:17:17
|
17 | type Item = ItemType;
| ^^^^^^^^ the trait `ATrait` is not implemented for `ItemType`
|
note: required by a bound in `Container::Item`
--> src/main.rs:11:16
|
11 | type Item: ATrait + BTrait;
| ^^^^^^ required by this bound in `Container::Item`

Rust compiler complaints about 2 almost identical closures

I have this simple struct with 2 Hashsets:
pub struct IpAddresses {
pub ipv4s: HashSet<String>,
pub ipv6s: HashSet<String>,
}
and then a simple function which is supposed to provide an iterator to one of the sets:
pub fn shared2(&self, ipv6: bool) -> impl Iterator<Item = IpAddr> + '_ {
if ipv6 {
self
.ipv6s
.iter()
.filter_map(|a| IpAddr::from_str(a).ok())
} else {
self
.ipv4s
.iter()
.filter_map(|a| IpAddr::from_str(a).ok())
}
}
I get the following error with the suggestion to use box:
error[E0308]: `if` and `else` have incompatible types
--> src/models/ip_address.rs:131:13
|
125 | / if ipv6 {
126 | | self
| _|_____________-
127 | | | .ipv6s
128 | | | .iter()
129 | | | .filter_map(|a| IpAddr::from_str(a).ok())
| |_|_____________________________________________________- expected because of this
130 | | } else {
131 | / | self
132 | | | .ipv4s
133 | | | .iter()
134 | | | .filter_map(|a| IpAddr::from_str(a).ok())
| |_|_____________________________________________________^ expected closure, found a different closure
135 | | }
| |_________- `if` and `else` have incompatible types
|
= note: expected type `FilterMap<std::collections::hash_set::Iter<'_, _>, [closure#src/models/ip_address.rs:129:25: 129:53]>`
found struct `FilterMap<std::collections::hash_set::Iter<'_, _>, [closure#src/models/ip_address.rs:134:25: 134:53]>`
= note: no two closures, even if identical, have the same type
= help: consider boxing your closure and/or using it as a trait object
help: you could change the return type to be a boxed trait object
|
122 | pub fn shared2(&self, ipv6: bool) -> Box<dyn Iterator<Item = IpAddr> + '_> {
| ~~~~~~~ +
help: if you change the return type to expect trait objects, box the returned expressions
|
126 ~ Box::new(self
127 | .shared_ipv6s
128 | .iter()
129 ~ .filter_map(|a| IpAddr::from_str(a).ok()))
130 | } else {
131 ~ Box::new(self
Interestingly enough if I copy paste one of the wings into a function, the compiler works fine without any error or need for a Box:
fn list_shared<'a>(&'a self, items: &'a HashSet<String>) -> impl Iterator<Item = IpAddr> + 'a {
items
.iter()
.filter_map(|a| IpAddr::from_str(a).ok())
}
pub fn shared<'a>(&'a self, ipv6: bool) -> impl Iterator<Item = IpAddr> + 'a {
if ipv6 {
self.list_shared(&self.ipv6s)
} else {
self.list_shared(&self.ipv4s)
}
}
As you can see this is a copy-paste of the inner block. Why is this happening? How are those 2 identical blocks not identical in the first instance but just putting them inside a function made them identical?
Each closure gets its own, anonymous type. Even though the closures have the same call signature, and even if neither of them borrows anything so no lifetimes are part of the signature, these types are not the same!
Therefore, the generic <F> in the returned FilterMap struct has a different type in each if branch, leading to the error message about trying to return incompatible types.
Note that -> impl Iterator tells the compiler that you're returning some type that implements Iterator, but it has to be statically the same type every time, determined at compile time.
When you extract the filter_map call to a separate function, there is only one closure, hence one type returned from that function. And since this is the same type for both if branches, the problem goes away.
It also goes away if you assign the closure to a variable, because then it's the same type in both cases as well:
pub fn shared2(&self, ipv6: bool) -> impl Iterator<Item = IpAddr> + '_ {
let from_str = |a: &String| IpAddr::from_str(a).ok();
if ipv6 {
self
.ipv6s
.iter()
.filter_map(from_str)
} else {
self
.ipv4s
.iter()
.filter_map(from_str)
}
}
impl Iterator<Item=IpAddr> is saying 'this function will return a static type that is determined at runtime that conforms to Iterator<Item=IpAddr>.
It is not the same as Box<dyn Iterator<Item=IpAddr> which means any type that conforms to Iterator<Item=IpAddr>.
The reason it dosent work is because each each one of |a| IpAddr::from_str(a).ok()) are diffrent types that are generated by the compiler, and filter_map rerurns a struct that has the iterator of the type FilterMap<I, F>, with F being the type of the function.
You can see the same issue if you move your closures to named functions
fn ipv6_iter(a: &String) -> Option<IpAddr> {
IpAddr::from_str(a).ok()
}
fn ipv4_iter(a: &String) -> Option<IpAddr> {
IpAddr::from_str(a).ok()
}
impl IpAddresses {
pub fn shared2(&self, ipv6: bool) -> impl Iterator<Item = IpAddr> + '_ {
if ipv6 {
self
.ipv6s
.iter()
.filter_map( ipv6_iter)
} else {
self
.ipv4s
.iter()
.filter_map( ipv4_iter)
}
}
}
In your first branch returns a FilterMap<IpAddresses, ipv6_iter>, but the else branch returns a FilterMap<IpAddresses, ipv4_iter>.
By moving the logic into list_shared, both filter_maps use the same anonymous function to do the mapping, therefore have the same FilterMap type.
Same as using the same static function in each filter_map
fn ip_iter(s: &String) -> Option<IpAddr> {
IpAddr::from_str(s).ok()
}
impl IpAddresses {
pub fn shared2(&self, ipv6: bool) -> impl Iterator<Item = IpAddr> + '_ {
if ipv6 {
self
.ipv6s
.iter()
.filter_map( ip_iter)
} else {
self
.ipv4s
.iter()
.filter_map( ip_iter)
}
}
}
So each branch returns a FilterMap<IpAddresses, ip_iter>, therefore impl Iterator<Item=IpAddr> has a single type to resolve to.

Is there a way to tell the compiler that nobody will implement a trait for a reference to a generic type?

Here is toy code that demonstrates the problem:
trait Foo {}
trait Boo<T> {
fn f() -> T;
}
impl<T> Boo<T> for i32
where
T: Foo,
{
fn f() -> T {
unimplemented!();
}
}
impl<'a, T> Boo<&'a T> for i32
where
T: Foo,
{
fn f() -> T {
unimplemented!();
}
}
I want to have two generic implementations of trait Boo, but it doesn't compile:
error[E0119]: conflicting implementations of trait `Boo<&_>` for type `i32`:
--> src/main.rs:16:1
|
7 | / impl<T> Boo<T> for i32
8 | | where
9 | | T: Foo,
10 | | {
... |
13 | | }
14 | | }
| |_- first implementation here
15 |
16 | / impl<'a, T> Boo<&'a T> for i32
17 | | where
18 | | T: Foo,
19 | | {
... |
22 | | }
23 | | }
| |_^ conflicting implementation for `i32`
|
= note: downstream crates may implement trait `Foo` for type `&_`
I do not plan to make this part of functionality to other crates. I tried:
moving this code to binary crate that obviously can not be used from other crates
moving this to a private mod
marking the trait as pub(crate)
all with no success.
Is there anyway to give the compiler a hint that it should not care that anybody will implement Foo for any reference?
Maybe my toy example is not the best, so here is the real code.
It's used for integration with the C part of my program, so it's a little
complicated.
impl<T: MyTrait> MyFrom<Option<T>> for *mut c_void {
fn my_from(x: Option<T>) -> Self {
match x {
Some(x) => <T>::alloc_heap_for(x),
None => ptr::null_mut(),
}
}
}
impl<'a, T: MyTrait> MyFrom<Option<&'a T>> for *mut c_void {
fn my_from(x: Option<&'a T>) -> Self {
match x {
Some(x) => x as *const T as *mut c_void,
None => ptr::null_mut(),
}
}
}
The conflict here doesn't have anything to do with the reference-ness of the latter implementation. The issue is that, in the first implementation, T can be any type, including reference types. Suppose you make the following function call:
let x: i32 = 10;
let result: &u8 = x.f();
At this point, the type resolver needs to figure out what function is being called. It finds a conflicting implementation:
impl Boo<&u8> for i32 via Boo<T> (T == &u8),
impl Boo<&u8> for i32 via Boo<&T> (T == u8),
You'd have exactly the same issue if you used a concrete type in the latter implementation:
// This will fail to compile
impl<T> Boo<T> for i32 { ... }
impl Boo<String> for i32 { ... }
This conflict means that the compiler can't allow these two implementations to coexist.
The specific thing you're looking to do here is called "specialization"; it refers to a proposal for a set of rules that says that overlapping implementations like this are allowed to exist if one of them is unambiguously more "specific" than the other, in which case the compiler will pick the more specific implementation. This is tracked as RFC #1210.

Resources