These are chat archives for rust-lang/rust

27th
Jan 2019
Gio Borje
@Hydrotoast
Jan 27 02:59

Hi all, I want to understand the how the type system handles impl Trait types. I have a simple example that has two implementations of "generic" identity functions for any type that implements Debug as an arbitrary trait. Could anyone explain how Rust determines the types of these two functions?

use std::fmt::Debug;

fn id_bound<A: Debug>(value: A) -> A { value }
fn id_free(value: impl Debug) -> impl Debug { value }

fn main() {
  // Compiles. Prints 11.
  println!("{:?}", id_bound(11));
  // Compiles. Prints 11.
  println!("{:?}", id_free(11));
}

For the first function, my expectation is that id_bound does not have a type until the type parameter is bound (due to monomorphization). This behavior is generally known. However, the type of id_free is unclear to me. I have poked at the compiler to get an idea, but perhaps more expert users could explain the type and how it is determined. :)

Yair Halberstadt
@YairHalberstadt
Jan 27 08:22
id_free is essentially sugar for id_bound. It mostly exists to be consistent with an impl return type, which works very differently.
Denis Lisov
@tanriol
Jan 27 08:39
Not exactly.
id_bound is a function that takes a value of any type that implements Debug and returns a value of the same type, while
id_free is a function that takes a value of any type that implements Debug and returns a value of some type that's guaranteed to implement Debug (but nothing else) and can depend on the input type
For example,
id_bound(1) + id_bound(10) compiles, because the compiler knows the "real" underlying type, while
id_free(1) + id_free(10) does not compile because the compiler does not know how to add impl Debug values
Denis Lisov
@tanriol
Jan 27 08:47
The second function is also monomorphized, so it does not have a concrete type until called, and after that it becomes fn(A) -> impl Debug, while the first becomes fn(A) -> A
Denis Lisov
@tanriol
Jan 27 08:54
Here impl Debug represents an anonymous opaque type that the compiler expects to be different for every monomorphization. Even if the "real" underlying type happens to be the same, they are not considered to be the same.
Yair Halberstadt
@YairHalberstadt
Jan 27 09:53
@tanriol true. I forgot that it returns an impl Debug, and doesn't just accept one.
Victor Lopes
@vlopes11
Jan 27 12:08

Hello Rustanceans. If I have a trait with a lifetime, and to send this trait as generic to a struct that will require this lifetime only for the trait declaration, then I need to use PhantomData.

trait SomeTrait<'a> {
    fn as_str(&self) -> &'a str;
}

struct SomeStruct<'a, T: SomeTrait<'a>> {
    attr: T
}

will produce

error[E0392]: parameter `'a` is never used
 --> src/main.rs:5:19
  |
5 | struct SomeStruct<'a, T: SomeTrait<'a>> {
  |                   ^^ unused type parameter
  |
  = help: consider removing `'a` or using a marker such as `std::marker::PhantomData`

To fix:

struct SomeStruct<'a, T: SomeTrait<'a>> {
    attr: T,
    phantom: std::marker::PhantomData<&'a T>,
}

This in my opinion is a bit weird. We are forced to declare an attribute that will never be used only to satisfy a compiler check for some supposedly not used lifetime, that's actually used by the trait as generic. Do you guys see this as a bad practice?

Gio Borje
@Hydrotoast
Jan 27 12:36

Thanks for the interpretations @YairHalberstadt and @tanriol . I have verified these interpretations by casting the generic functions with applied types to unity:

// Inferred type: fn(i32) -> i32 {id_bound::<i32>}
id_bound::<i32> as ();

// Inferred type: fn(i32) -> impl std::fmt::Debug {id_free::<i32>}
id_free::<i32> as ();

As @tanriol mentioned, the id_free function does not resolve the return type to a concrete type. With the hint from @YairHalberstadt , I discovered that we can bind the argument type in id_free as generic arguments in a path expression; this was not obvious to me from the docs.

Gio Borje
@Hydrotoast
Jan 27 12:42

@vlopes11 I believe you can use "higher-ranked trait bounds" to solve this problem, which universally quantifies over all lifetime bounds:

struct SomeStruct<T: for<'a> SomeTrait<'a>>

See https://doc.rust-lang.org/reference/trait-bounds.html#higher-ranked-trait-bounds for details.

Denis Lisov
@tanriol
Jan 27 20:04
@vlopes11 Note that the exact marker type may depend on the specifics of your struct and trait. For example, in this case I'd probably use PhantomData<fn() -> &'a str>