Type inference picks the wrong supertrait - rust

This code:
trait Foo: PartialOrd + PartialOrd<<Self as Foo>::A> {
type A;
}
trait Bar {
type B: Foo;
fn bar(&self) -> Self::B;
}
fn baz<T: Bar>(x: T, y: T) -> bool {
x.bar() < y.bar()
}
fails to compile with this error:
error[E0308]: mismatched types
--> src/lib.rs:12:15
|
12 | x.bar() < y.bar()
| ^^^^^^^ expected Foo::A, found Bar::B
|
= note: expected associated type `<<T as Bar>::B as Foo>::A`
found associated type `<T as Bar>::B`
which seems to imply that type inference is trying to call the B: PartialOrd<B::A> impl instead of the B: PartialOrd<B> impl which is also available.
Is this a bug or just an (intentional/unintentional) limitation of Rust's type inference?
Is there a better workaround than this:
<T::B as PartialOrd>::partial_cmp(&x.bar(), &y.bar()) == Some(Ordering::Less)

Related

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`

Type mismatch returning associated trait from generic function in trait

Why does this code fail to compile? Playground
pub trait Dataset {}
pub trait Property {
type Value;
fn value<D: Dataset>(_ds: D) -> Self::Value
where
Self: DatasetProperty<D>;
}
pub trait DatasetProperty<D>
where
Self: Property,
D: Dataset,
{
}
impl<T> Property for T {
type Value = String;
fn value<D: Dataset>(_ds: D) -> Self::Value
where
Self: DatasetProperty<D>,
{
String::new()
}
}
This errors with:
error[E0308]: mismatched types
--> src/lib.rs:23:9
|
18 | type Value = String;
| -------------------- expected this associated type
19 | fn value<D: Dataset>(_ds: D) -> Self::Value
| ----------- expected `<T as Property>::Value` because of return type
...
23 | String::new()
| ^^^^^^^^^^^^^ expected associated type, found struct `std::string::String`
|
= note: expected associated type `<T as Property>::Value`
found struct `std::string::String`
The error points to line 18 where the associated type is set to String, but then it's saying String was not expected. This code works if I remove the where clause in the value function.

Why does Rust fail to pick the right trait in this case?

Consider the following code:
use std::ops::Add;
trait Trait
where Self::Assoc: Add<Self::Assoc, Output=Self::Assoc>
+ for <'a> Add<&'a Self::Assoc, Output=Self::Assoc>
{
type Assoc;
fn get(&self) -> Self::Assoc;
}
fn add1<T: Trait>(x: T, y: T) -> T::Assoc {
x.get() + y.get()
}
This fails to compile with:
error[E0308]: mismatched types
--> src/lib.rs:12:15
|
12 | x.get() + y.get()
| ^^^^^^^
| |
| expected reference, found associated type
| help: consider borrowing here: `&y.get()`
|
= note: expected reference `&<T as Trait>::Assoc`
found associated type `<T as Trait>::Assoc`
I'm able to work around the issue by explicitly specifying what trait I want to use:
fn add2<T: Trait>(x: T, y: T) -> T::Assoc {
<T::Assoc as Add<T::Assoc>>::add(x.get(), y.get())
}
But I'm wondering: why is this happening? Also, is there any more compact work around?
Note that the order of the trait bounds matters. If I change them to:
where Self::Assoc: for <'a> Add<&'a Self::Assoc, Output=Self::Assoc>
+ Add<Self::Assoc, Output=Self::Assoc>
... then add1 compiles fine, but this fails:
fn add3<T: Trait>(x: T, y: T) -> T::Assoc {
x.get() + &y.get()
}
Link to playground

Using impl Trait in a recursive function

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

"expected type parameter, found struct" when implementing trait

I am trying to create a trait for a directed graph structure and provide one very basic implementatio, but am running into compiler errors:
pub trait DiGraph<'a> {
type N;
fn nodes<T>(&'a self) -> T where T: Iterator<Item=&'a Self::N>;
fn pre<T>(&'a self, node: Self::N) -> T where T: Iterator<Item=&'a Self::N>;
fn succ<T>(&'a self, node: Self::N) -> T where T: Iterator<Item=&'a Self::N>;
}
struct SimpleNode {
pre: Vec<usize>,
succ: Vec<usize>,
}
pub struct SimpleDiGraph {
pub nodes: Vec<SimpleNode>
}
impl<'a> DiGraph<'a> for SimpleDiGraph {
type N = usize;
fn nodes<T=std::ops::Range<usize>>(&'a self) -> T {
return std::ops::Range { start: 0, end: self.nodes.len() };
}
fn pre<T=std::slice::Iter<'a,usize>>(&'a self, node: usize) -> T {
return self.nodes[node].pre.iter();
}
fn succ<T=std::slice::Iter<'a,usize>>(&'a self, node: usize) -> T {
return self.nodes[node].succ.iter();
}
}
The error messages are:
error[E0308]: mismatched types
--> digraph.rs:21:16
|
21 | return std::ops::Range { start: 0, end: self.nodes.len() };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter, found struct `std::ops::Range`
|
= note: expected type `T`
= note: found type `std::ops::Range<usize>`
error[E0308]: mismatched types
--> digraph.rs:24:16
|
24 | return self.nodes[node].pre.iter();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter, found struct `std::slice::Iter`
|
= note: expected type `T`
= note: found type `std::slice::Iter<'_, usize>`
error[E0308]: mismatched types
--> digraph.rs:27:16
|
27 | return self.nodes[node].succ.iter();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter, found struct `std::slice::Iter`
|
= note: expected type `T`
= note: found type `std::slice::Iter<'_, usize>`
The error message is somewhat confusing to me - why would a type parameter be expected as return value? Is this simply a type mismatch (e.g. due to lifetimes) with a misleading error message?
<T=std::ops::Range<usize>> doesn't force T to be std::ops::Range<usize>, it just causes it to default to that if it doesn't know what else to use.
If you only ever want to return a Range<usize>, then use Range<usize> as the return type; there's no reason to have a generic parameter at all. What your code is effectively saying now is something like this:
"You can pick any return type you want! If you don't care, I'll return a range<usize>."
"I'd like you to return a String, please."
"TOUGH you're getting a range<usize>!"
"... but you said..."
"I lied! MUAHAHAHAHAHA!"
If you actually want the caller to choose the return type, then you need to be prepared to return any T... which is almost impossible, since it could be anything from () to String to an OpenGL render context.
In that case, what you actually want to do is constrain T to some trait that requires types implement some sort of construction function. Default is an example of one.
Edit: Just to doubly clarify: you don't get to pick the types used in generic parameters, your caller does.
I didn't notice until just now that you're using different definitions in the trait and the implementation (don't do that). I assume that what you're really trying to do is say "this method returns something that's usable as an Iterator, but each impl can pick a different type." You cannot do this with generics.
What you want is an associated type on the trait, like so:
pub trait DiGraph<'a> {
type Nodes;
fn nodes(&'a self) -> Self::Nodes;
}
pub struct SimpleNode {
pre: Vec<usize>,
succ: Vec<usize>,
}
pub struct SimpleDiGraph {
pub nodes: Vec<SimpleNode>
}
impl<'a> DiGraph<'a> for SimpleDiGraph {
type Nodes = std::ops::Range<usize>;
fn nodes(&'a self) -> Self::Nodes {
return std::ops::Range { start: 0, end: self.nodes.len() };
}
}

Resources