Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trait alias compilation failure when returning impl Alias #67751

Open
hniksic opened this issue Dec 31, 2019 · 5 comments
Open

Trait alias compilation failure when returning impl Alias #67751

hniksic opened this issue Dec 31, 2019 · 5 comments
Labels
A-impl-trait C-bug F-trait_alias requires-nightly T-compiler

Comments

@hniksic
Copy link
Contributor

@hniksic hniksic commented Dec 31, 2019

While working on a stackoverflow question I encountered the need for trait aliases. For example, this code (playground) uses a very long trait several times:

fn to_box_factory<F, RET>(f: F) -> impl Fn(&'static str) -> Box<dyn Iterator<Item = &'static str>>
where
    F: Fn(&'static str) -> RET,
    RET: Iterator<Item = &'static str> + 'static,
{
    move |s| Box::new(f(s))
}

fn main() {
    let f1: &dyn Fn(&'static str) -> Box<dyn Iterator<Item = &'static str>> = &to_box_factory(|s| s.split_whitespace());
    let f2: &dyn Fn(&'static str) -> Box<dyn Iterator<Item = &'static str>> = &to_box_factory(|s| s.split_ascii_whitespace());
    let fs = vec![f1, f2];
    fs[0]("rust 2020").for_each(|s| println!("{}", s));
    fs[1]("rust 2020").for_each(|s| println!("{}", s));
}

It would improve readability and reduce duplication to introduce a trait alias for Fn(&'static str) -> Box<dyn Iterator<Item = &'static str>>. My attempt was to simply replace all uses of the trait with the alias, resulting in the following (playground):

#![feature(trait_alias)]
trait BoxFactory = Fn(&'static str) -> Box<dyn Iterator<Item = &'static str>>;

fn to_box_factory<F, RET>(f: F) -> impl BoxFactory
where
    F: Fn(&'static str) -> RET,
    RET: Iterator<Item = &'static str> + 'static,
{
    move |s| Box::new(f(s))
}

fn main() {
    let f1: &dyn BoxFactory = &to_box_factory(|s| s.split_whitespace());
    let f2: &dyn BoxFactory = &to_box_factory(|s| s.split_ascii_whitespace());
    let fs = vec![f1, f2];
    fs[0]("rust 2020").for_each(|s| println!("{}", s));
    fs[1]("rust 2020").for_each(|s| println!("{}", s));
}

However, that fails to compile with the following error:

error[E0271]: type mismatch resolving `<[closure@src/main.rs:9:5: 9:28 f:_] as std::ops::FnOnce<(&'static str,)>>::Output == std::boxed::Box<(dyn std::iter::Iterator<Item = &'static str> + 'static)>`
 --> src/main.rs:4:36
  |
4 | fn to_box_factory<F, RET>(f: F) -> impl BoxFactory
  |                      ---           ^^^^^^^^^^^^^^^ expected type parameter `RET`, found trait `std::iter::Iterator`
  |                      |
  |                      this type parameter
  |
  = note: expected struct `std::boxed::Box<RET>`
             found struct `std::boxed::Box<(dyn std::iter::Iterator<Item = &'static str> + 'static)>`
  = help: type parameters must be constrained to match other types
  = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters
  = note: the return type of a function must have a statically known size

I'm not sure I understand that error, nor do I understand why the error doesn't appear in the first version.

Curiously, if I just replace -> impl BoxFactory with the full incantation of -> impl Fn(&'static str) -> Box<dyn Iterator<Item = &'static str>> (playground), but leave the other two uses of the alias in main(), the code compiles and runs correctly. Is it a bug that returning impl Alias fails, while returning impl [original trait] works?

Note: I am aware that trait aliases are experimental. My motivation for reporting this issue is to ensure that problems with the feature (if any) are worked out before it is stabilized.

@Centril Centril added F-trait_alias A-impl-trait T-compiler C-bug requires-nightly labels Dec 31, 2019
@hniksic
Copy link
Contributor Author

@hniksic hniksic commented Nov 9, 2020

While experimenting with something else, I figured out how to make this example compile. It seems that the type inference of the Box::new expression in the closure is incomplete, because it compiles when as _ is added after it, i.e. when move |s| Box::new(f(s)) is changed to move |s| Box::new(f(s)) as _.

I'm not sure that as _ should be required here, and the diagnostics could likely be better. I hope this helps with labeling of this bug.

@hniksic
Copy link
Contributor Author

@hniksic hniksic commented Nov 9, 2020

Note that the same type inference/diagnostic issue is present when you emulate a type alias with a custom trait with a blanket implementation, so it's not specific to trait aliases. Instead of a BoxFactory alias, we can define a new trait with a blanket implementation:

trait BoxFactory: Fn(&'static str) -> Box<dyn Iterator<Item = &'static str>> {}

impl<T> BoxFactory for T where T: Fn(&'static str) -> Box<dyn Iterator<Item = &'static str>> {}

Then the original to_box_factory results in a very similar compilation error (playground):

fn to_box_factory<F, RET>(f: F) -> impl BoxFactory
where
    F: Fn(&'static str) -> RET,
    RET: Iterator<Item = &'static str> + 'static,
{
    move |s: &'static str| Box::new(f(s))
}

// error[E0271]: type mismatch resolving `<[closure@src/lib.rs:10:5: 10:42 f:_] as std::ops::FnOnce<(&'static str,)>>::Output == std::boxed::Box<(dyn std::iter::Iterator<Item = &'static str> + 'static)>`
//  --> src/lib.rs:5:36
//   |
// 5 | fn to_box_factory<F, RET>(f: F) -> impl BoxFactory
//   |                      ---           ^^^^^^^^^^^^^^^ expected type parameter `RET`, found trait object `dyn std::iter::Iterator`
//   |                      |
//   |                      this type parameter
//   |
//   = note: expected struct `std::boxed::Box<RET>`
//              found struct `std::boxed::Box<(dyn std::iter::Iterator<Item = &'static str> + 'static)>`
//   = help: type parameters must be constrained to match other types
//   = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters
//   = note: required because of the requirements on the impl of `BoxFactory` for `[closure@src/lib.rs:10:5: 10:42 f:_]`
//   = note: the return type of a function must have a statically known size

...whereas the version with as _ compiles and runs as expected.

@jyn514
Copy link
Member

@jyn514 jyn514 commented Jul 7, 2021

The original code now compiles and runs.

@jyn514 jyn514 closed this Jul 7, 2021
@hniksic
Copy link
Contributor Author

@hniksic hniksic commented Jul 7, 2021

@jyn514 What compiler version does it compile with? When I try it with the 1.55.0-nightly (2021-07-05) currently in the Playground, it fails with the same error message as before. Note that the first snippet is just a setup, the second one is the one that actually fails.

@jyn514
Copy link
Member

@jyn514 jyn514 commented Jul 8, 2021

Oh sorry, I misread the original issue. Yes, the second one still fails on nightly.

@jyn514 jyn514 reopened this Jul 8, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-impl-trait C-bug F-trait_alias requires-nightly T-compiler
Projects
None yet
Development

No branches or pull requests

3 participants