Trait mismatch for function argument - rust

I've got one piece of Rust code that compiles and one that's very similar that does not.
The one that works:
pub fn do_something(_: Box<Iterator<Item = f64>>) {}
fn main() {
let iter = Box::new(vec![1.0].into_iter());
do_something(iter);
}
The one that fails:
pub fn do_something(_: Box<Box<Iterator<Item = f64>>>) {}
fn main() {
let iter = Box::new(Box::new(vec![1.0].into_iter()));
do_something(iter);
}
The difference is I have a Box<Box<..>> instead of a Box<..>
I get the following error:
error[E0308]: mismatched types
--> src/main.rs:5:18
|
5 | do_something(iter);
| ^^^^ expected trait std::iter::Iterator, found struct `std::vec::IntoIter`
|
= note: expected type `std::boxed::Box<std::boxed::Box<std::iter::Iterator<Item=f64> + 'static>>`
found type `std::boxed::Box<std::boxed::Box<std::vec::IntoIter<{float}>>>`
I'm interpreting this error to say "IntoIter does not have the trait Iterator" .. but it does. What's the issue?

You can't coerce a Box<Box<I>> into a Box<Box<Iterator<Item = f64>>>, for reasons discussed in this question, but you can coerce the inner Box:
pub fn do_something(_: Box<Box<Iterator<Item = f64>>>) {}
fn main() {
let iter = Box::new(Box::new(vec![1.0].into_iter()) as Box<Iterator<Item = f64>>);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
do_something(iter);
}
Playground.
This works because a cast is a coercion site. By writing as Box<Iterator<Item = f64>>, you're hinting to the compiler that it should attempt to make the expression to the left fit that type instead of inferring Box<IntoIter<f64>>, because once it's wrapped up in the "outer" Box, you can't change it anymore.
Alternatively (but less clearly), you could make Box::new(...) a coercion site by explicitly parameterizing Box:
let iter = Box::<Box<Iterator<Item = f64>>>::new(Box::new(vec![1.0].into_iter()));
Which effectively does the same thing.

To be honest, I'm no expert in Rust at all, but my expectation would have been that both of the snippets you show do not compile. That is because, as you pointed out, Iterator is a trait and not a type and basically you want do_something to receive any type which implements Iterator. Maybe there exists a shortcut such that the compiler can transform the signature into a generic if one of the types is a trait which could be why is sometimes works, but then I'm also not familiar with the Rust language specification enough.
Instead of having do_something take something of type Iterator (?) make it a generic of type T where T is trait bound.
pub fn do_something<T>(_: Box<Box<T>>)
where T: Iterator<Item = f64> + Send {}
fn main() {
let iter = Box::new(Box::new(vec![1.0].into_iter()));
do_something(iter);
}
Playground
Alternatively, you constrain do_something entirely to std::vec::IntoIter and only take parameters of that type.
pub fn do_something(_: Box<Box<std::vec::IntoIter<f64>>>) {}
fn main() {
let iter = Box::new(Box::new(vec![1.0].into_iter()));
do_something(iter);
}
Playground

Related

`impl Trait` return type causes wrong lifetime elision

The impl Trait syntax for return types seems to cause the compiler to incorrectly assume that the lifetime of the input argument must match the output in some situations. Consider the function
fn take_by_trait<T: InTrait>(_: T) -> impl OutTrait {}
If the input type contains a lifetime the compiler complains when the output outlives the it even though they are completely independent. This will not happen if the input type is not generic, or if the output is a Box<dyn OutTrait>.
Full code:
trait InTrait {}
struct InStruct<'a> {
_x: &'a bool, // Just a field with some reference
}
impl<'a> InTrait for InStruct<'a> {}
trait OutTrait {}
impl OutTrait for () {}
fn take_by_type(_: InStruct) -> impl OutTrait {}
fn take_by_trait<T: InTrait>(_: T) -> impl OutTrait {}
fn take_by_trait_output_dyn<T: InTrait>(_: T) -> Box<dyn OutTrait> {
Box::new(())
}
fn main() {
let _ = {
let x = true;
take_by_trait(InStruct{ _x: &x }) // DOES NOT WORK
// take_by_type(InStruct { _x: &x }) // WORKS
// take_by_trait_output_dyn(InStruct { _x: &x }) // WORKS
};
}
Is there some elided lifetime here that I could qualify to make this work, or do I need to do heap allocation?
The impl Trait semantic means that the function returns some type that implements the Trait, but the caller cannot make any assumptions about which type that would be or what lifetimes it would use.
For all the compiler knows the take_by_trait function can be used in many different modules, or, probably, in other crates. Now, your implementation would work fine in all use cases. It can be rewritten like
fn take_by_trait<T: InTrait>(_: T) -> () {}
This is a perfectly fine function and will work just fine. But then at some point you might want to add another implementation for the OutTrait and change your take_by_trait function a little.
trait OutTrait { fn use_me(&self) {} }
impl<T: InTrait> OutTrait for T {}
fn take_by_trait<T: InTrait>(v: T) -> impl OutTrait {v}
If we expand the generic parameter and impl definition, we get this code:
fn take_by_trait<'a>(v: InStruct<'a>) -> InStruct<'a> {v}
fn main() {
let v = {
let x = true;
take_by_trait(InStruct{ _x: &x })
};
v.use_me();
}
This obviously cannot work because x is dropped before println! tries to access its value. So by adding a new implementation for the OutTrait you broke code that uses this function, potentially somewhere in a crate that depends on yours. That's why the compiler is reluctant to allow you defining such things.
So, again, the issue with impl OutTrait is just that the compiler cannot make any assumptions about the returned type and its lifetime, so it uses the maximum possible bound, which produces the borrow checker error you see.
EDIT: I've modified the code a little so that the signature of the function would not change and the code actually compiles and produces the same lifetime error: playground
impl Trait in return position implicitly captures the any lifetime appearing in generic parameters. This is not something you can express in normal Rust, only with impl Trait.
On stable as far as I know there is no way to avoid that. On nightly you can use type_alias_impl_trait:
#![feature(type_alias_impl_trait)]
type Ret = impl OutTrait;
fn take_by_trait<T: InTrait>(v: T) -> Ret {}
See issues Unclear compiler error when impl Trait return value captures non-'static argument (#82171), impl Trait capturing lifetime of type parameter (#79415), False-positive "temporary dropped while borrowed" involving return-position impl Trait (#98997), impl Trait + 'static is not static if returned from generic function (#76882).

Implement trait that has function which return traits

I am trying to implement a trait for a struct which in turn has functions that return traits.
I want this, because I do not want to bind the uer to a specific data structure.
However, trying to apply the compiler's correction suggestions, I fell deeper and deeper into a rabbit hole to no avail.
Here's a minimal example of what I'm trying to do:
trait WordsFilter {
fn starting_with(&self, chr: char) -> dyn Iterator<Item = String>;
}
struct WordsContainer {
words: Vec<String>,
}
impl WordsFilter for WordsContainer {
fn starting_with(&self, chr: char) -> dyn Iterator<Item = String>
{
self.words.iter().filter(|word| word.starts_with("a"))
}
}
fn main() {}
Which results in:
error[E0277]: the size for values of type `(dyn Iterator<Item = String> + 'static)` cannot be known at compilation time
--> .\traits.rs:10:40
|
10 | fn starting_with(&self, chr: char) -> dyn Iterator<Item = String>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `Sized` is not implemented for `(dyn Iterator<Item = String> + 'static)`
= note: the return type of a function must have a statically known size
error: aborting due to previous error
For more information about this error, try `rustc --explain E0277`.
I tried to apply the compiler's correction's step by step but they were just getting more.
TL;DR Use generics or associated types.
You cannot use bare dyn Trait types. They are Unsized which means that compiler cannot know their size at the compile time. Therefore you can only use them behind some reference (similarly to how you cannot use str or [T] and must use &str and &[T]). The easiest way is to use Box<dyn Trait>, like PitaJ earlier suggested. This requires of course one heap allocation, but allows a very nice API.
In normal functions and methods you can however use impl Trait trick to make compiler infer returned type. This is used like this
fn foo() -> impl Iterator<Item = ()> {
todo!()
}
Calling this function would return some concrete object that implements given trait. This however is currently not possible in Trait definitions. There is active development and hopefully in the future we will be able to use it. However now it's not the case. So following code will not compile!
// XXX: This doesn't currently work!
trait Foo {
fn foo() -> impl Iterator<Item = ()>;
}
There is luckily a simple solution that wouldn't require boxing trait objects. You can define an associated type and require each implementator to specify a concrete type. You can also require that this type must implement the Iterator trait.
trait WordsFilter {
type Iter: Iterator<Item = String>;
fn starting_with(&self, chr: char) -> Self::Iter;
}
This would make each implementation specify one concrete type of returned iterator. If you would like to be able to return multiple different iterators for a single type you can use generics.
trait WordsFilter<I>
where
I: Iterator<Item = String>
{
fn starting_with(&self, chr: char) -> I;
}
There are several things that need to be fixed to get this to work.
I'll first show the workng version then step through what had to change and why.
trait WordsFilter {
fn starting_with(&self, chr: char) -> Box<dyn Iterator<Item = String> + '_>;
}
struct WordsContainer {
words: Vec<String>,
}
impl WordsFilter for WordsContainer {
fn starting_with(&self, chr: char) -> Box<dyn Iterator<Item = String> + '_>
{
Box::new(self.words.iter().filter(|word| word.starts_with("a")).cloned())
}
}
fn main() {}
Boxing the iterator. Rust doesn't allow you to return naked dyn types. The compiler error tells you to Box it. So I did.
Clone after filter. Vec<X>::iter() returns an iterator with Item=&X so in this example we get Item=&String we want Item=String so we need to clone.
Add a lifetime. The problem was that without the lifetime checks I could write this...
let words_container = WordsContainer{...};
let it = words_container.starting_with();
drop(words_container)
it.next()
but it.next() is stepping through the internal vector that is inside words_container - which no longer exists.
Adding the '_ lifetime on the trait is saying the iterator is only valid for as long as the underlying container is. So now the above code would generate a compile error.

implementing traits for dyn Fns

Today I was playing around with function traits. Though the example I show below might not practically be very useful, I do wonder why it doesn't compile.
pub fn do_something(o: &(dyn Other + 'static)) {
}
trait Other {
fn do_something_other(&self);
}
impl<A> Other for dyn Fn(A) {
fn do_something_other(&self) {
do_something(self);
}
}
Here I implement a trait for a function type. This function type is generic over it's parameter. This means that if you were to do it like this:
pub fn do_something(o: &(dyn Other + 'static)) {
}
trait Other {
fn do_something_other(&self);
}
impl<F, A> Other for F where F: (Fn(A)) + 'static {
fn do_something_other(&self) {
do_something(self);
}
}
you get an error stating a type parameter is unconstrained.
I get this and don't believe it's possible to do it with generics. But the dynamic approach, why doesn't it work? It gives the following error:
I don't understand this error. It states I pass a Fn(A) -> (), which doesn't implement Other. However, this error occurs literally in the implementation of Other. How can it not be implemented here?
My first thought was because each closure is its own type. If it has to do with this, I find the error very weird.
The first construction fails because you cannot convert a &dyn A into a &dyn B, even when implementing B for dyn A.
trait A {}
trait B {
fn do_thing(&self);
}
impl B for dyn A {
fn do_thing(&self) {
let b: &dyn B = self;
}
}
error[E0308]: mismatched types
--> src/lib.rs:9:25
|
9 | let b: &dyn B = self;
| ------ ^^^^ expected trait `B`, found trait `A`
| |
| expected due to this
|
= note: expected reference `&dyn B`
found reference `&(dyn A + 'static)`
Well, you can convert traits but only with help from the source trait. But since in this case the source is Fn, that's not a route.
The second construction fails because Rust won't let you implement traits that can conflict. Trying to implement B for a type that implements A<_> will automatically be rejected because types can have multiple implementations of A<_>.
trait A<T> {}
trait B {
fn do_thing(&self);
}
impl<T, U> B for T where T: A<U> {
fn do_thing(&self) {}
}
error[E0207]: the type parameter `U` is not constrained by the impl trait, self type, or predicates
--> src/lib.rs:7:9
|
7 | impl<T, U> B for T where T: A<U> {
| ^ unconstrained type parameter
Regarding Fns in particular, its somewhat hard to tell since usually function objects only implement a single Fn trait. However, the keyword is usually since you can enable a feature on nightly to do just that. And the trait system usually doesn't play favorites.
So what can you do? Well the first method is still functional, just you have to keep the implementation within the trait. You can use the second method if you use a concrete types for the function arguments.
You can conceivably implement Other for &dyn Fn(_) (implementing it on the reference and not the object itself). But that's not particularly convenient with how Fn objects are usually used.
pub fn do_something(o: &dyn Other) {}
trait Other {
fn do_something_other(&self);
}
impl<A> Other for &dyn Fn(A) {
fn do_something_other(&self) {
do_something(self);
}
}
fn main() {
// THIS WORKS
let closure: &dyn Fn(_) = &|x: i32| println!("x: {}", x);
closure.do_something_other();
// THIS DOESN'T WORK
// let closure = |x: i32| println!("x: {}", x);
// closure.do_something_other();
}
Another option would be to make the Other trait generic in order to constrain A, but that of course depends on how its designed to be used.

Why can't I push into a Vec of dyn Trait unless I use a temporary variable?

This is my code:
use std::rc::{Rc, Weak};
use std::cell::RefCell;
trait Trait {}
fn push<E: Trait>(e: E) {
let mut v: Vec<Rc<RefCell<Box<dyn Trait>>>> = Vec::new();
// let x = Rc::new(RefCell::new(Box::new(e)));
// v.push(x); // error
v.push(Rc::new(RefCell::new(Box::new(e)))); // works fine
}
The v.push(x) raises this error:
error[E0308]: mismatched types
--> src/main.rs:12:12
|
7 | fn push<E: Trait>(e: E) {
| - this type parameter
...
12 | v.push(x);
| ^ expected trait object `dyn Trait`, found type parameter `E`
|
= note: expected struct `std::rc::Rc<std::cell::RefCell<std::boxed::Box<dyn Trait>>>`
found struct `std::rc::Rc<std::cell::RefCell<std::boxed::Box<E>>>`
But if I push the value (constructed with the exact same value and types) directly into the vector it compiles without error.
So why doesn't the first version compile? And what should I change to make it so that I can use x before pushing it into the vector?
It's all in the type inference. When you write:
v.push(Rc::new(RefCell::new(Box::new(e))));
Rust can tell from that context that the argument to RefCell::new() must be a Box<dyn Trait>, so despite supplying a Box<E>, it coerces it to the former type. When you write this on the other hand:
let x = Rc::new(RefCell::new(Box::new(e)));
v.push(x); // compile error
Rust first infers that x of type Rc<RefCell<Box<E>>> and you can no longer push it into a vec of Rc<RefCell<Box<dyn Trait>>>. You can change this by putting an explicit type annotation in your let binding to tell Rust upfront that you really do want a Rc<RefCell<Box<dyn Trait>>>:
use std::rc::{Rc, Weak};
use std::cell::RefCell;
trait Trait {}
fn push<E: Trait>(e: E) {
let mut v: Vec<Rc<RefCell<Box<dyn Trait>>>> = Vec::new();
let x: Rc<RefCell<Box<dyn Trait>>> = Rc::new(RefCell::new(Box::new(e)));
v.push(x); // compiles
}
playground
The important thing to understand here is that E is not the same as dyn Trait. E is some known concrete implementation of Trait while dyn Trait is a trait object with its underlying concrete implementation erased.

Why is an explicit dereference required in (*x).into(), but not in x.my_into()?

After reading method-call expressions, dereference operator, method lookup, and auto-dereferencing, I thought I had a pretty good understanding of the subject; but then I encountered a situation in which I expected auto-dereferencing to happen, when in fact it didn't happen.
The example is as follows.
#[derive(Clone, Copy, Debug)]
struct Foo();
impl Into<&'static str> for Foo {
fn into(self) -> &'static str {
"<Foo as Into>::into"
}
}
fn vec_into<F: Copy + Into<T>, T>(slice: &[F]) -> Vec<T> {
slice.iter().map(|x| (*x).into()).collect()
}
fn main() {
let array = [Foo(), Foo(), Foo()];
let vec = vec_into::<_, &'static str>(&array);
println!("{:?}", vec);
}
The code above works, but I thought that the explicit dereferencing (*x).into() in the function vec_into wasn't needed. My reasoning is that, since x: &Foo, then x.into() would try to find methods accepting type &Foo, &&Foo, &mut &Foo, Foo, &Foo, &mut Foo.
This is because there is the chain of dereferencing &Foo → Foo, and for each U in this chain we insert also &U and &mut U.
My intuition is confirmed by the fact that the following code also works, without any explicit dereference.
#[derive(Clone, Copy, Debug)]
struct Foo();
trait MyInto<T> {
fn my_into(self) -> T;
}
impl MyInto<&'static str> for Foo {
fn my_into(self) -> &'static str {
"<Foo as MyInto>::my_into"
}
}
fn vec_my_into<F: Copy + MyInto<T>, T>(slice: &[F]) -> Vec<T> {
slice.iter().map(|x| x.my_into()).collect()
}
fn main() {
let array = [Foo(), Foo(), Foo()];
let my_vec = vec_my_into(&array);
println!("{:?}", my_vec);
}
Here x: &Foo is implicitly dereferenced in order to call the method <Foo as MyInto<&'static str>>::my_into.
A smaller example
Given the above definitions of Foo and MyInto, the code
let result: &str = (&Foo()).my_into()
works, but
let result: &str = (&Foo()).into()
fails to compile with the error
error[E0277]: the trait bound `&str: std::convert::From<&Foo>` is not satisfied
--> src/bin/into.rs:34:33
|
34 | let result: &str = (&Foo()).into();
| ^^^^ the trait `std::convert::From<&Foo>` is not implemented for `&str`
|
= note: required because of the requirements on the impl of `std::convert::Into<&str>` for `&Foo`
Rust performs method lookup exatly as you describe, and it immediately finds a candidate for .into() – the blanket implementation
impl<T, U> Into<U> for T
where
U: From<T>,
{
fn into(self) -> U {
U::from(self)
}
}
This implementation fulfils all the requirements for candidate methods – it is visible, in scope and defined for type &Foo, since it is defined for any type T. Once the compiler has picked this candidate, it notices that the trait bounds on U are not satisfied, and issues the error you see.
The situation for MyInto is completely different, because you don't provide a blanket implementation based on From. If you do, you will get the same error.
It could be argued that the compiler should skip the blanket implementation if the trait bounds are not satisfied, and move on with the list of candidate types until it finds a better fit. The language specification is actually not completely clear on that point, but from the error we see it is clear what the compiler actually does.
Interestingly enough, after inspecting the compiler output, it would appear that the Into implementation actually just calls a method of the trait From. What it's looking for is std::collections::From<&Foo> for &str. Therefore, if we implement that trait, the compiler will indeed find our function, and execute it.
Using the following declarations:
#[derive(Clone, Copy, Debug)]
struct Foo();
impl std::convert::From<&Foo> for &str {
fn from(f: &Foo) -> &'static str {
"A &str"// This could be replaced with an actual implementation
}
}
Your code works as you wanted:
let result: &str = (&Foo()).into();// No compiler errors.
The original code you wanted does work, and it isn't actually hard to implement.

Resources