I'm having trouble understanding how the following example, distilled from this code, compiles:
trait A: B {}
trait B {}
impl<T> B for T where T: A {}
struct Foo;
impl A for Foo {}
fn main() {}
My current understanding is that
trait A: B declares a trait A with the supertrait B. The Rust reference on traits states
Supertraits are traits that are required to be implemented for a type to implement a specific trait.
impl<T> B for T where T:A implements B for any type with the trait A
.
I expect impl A for Foo to fail because before A is implemented for Foo, the blanket implementation can't implement B for Foo, which is required.
My most plausible model for what rustc does while compiling the snippet is as follows:
implement A for Foo, deferring the check that Foo implements B to a later stage
implement B for Foo with the blanket implementation, since Foo now implements A
check that Foo implements B as required by the trait bound A: B
Is this in some way close to the truth? Is there any documentation I missed explaining the order in which implementations are processed?
rustc doesn't work "in order". Rather, we first register all impls and then type-check each impl with no particular order. The idea is that we collect a list of obligations (of various kinds - one of them is a trait bound), and then we match them against impls (not just; this is only one way to resolve an obligation, but this is what relevant here). Each obligation can create another, recursive obligations and we elaborate them until there are no more.
The way it currently works is that when we check an impl Trait for Type, we add an obligation Type: Trait. This might seem silly, but we later elaborate it further until all required bounds are met.
So let's say we're currently checking impl<T> B for T where T: A. We add one obligation, T: B, and match it against impl B for T. There is nothing to elaborate further, so we finish successfully.
We then check impl A for Foo, and add an obligation Foo: A. Since the trait A requires Self: B, we add another obligation Foo: B. Then we start matching obligations: the first obligation, Foo: A, is matched by the currently processed impl with no additional obligations. The second obligation, Foo: B, is matched against impl<T> B for T where T: A. This has a new obligate - T: A or Foo: A - so we try to match that. We successfully match that against impl A for Foo, with no additional obligations.
An interesting implication of the above is that if we change the second impl to the following:
impl A for Foo where Foo: B {}
Then this no longer compiles with an "overflow evaluating the requirement Foo: A" error (playground), even though it is essentially the same, because now to prove that Foo: A rustc needs to prove that Foo: B and again that Foo: A, while previously it just registered an obligation for Foo: B and not proved it immediately.
Note: The above is an over-simplification: for example, there is also a cache, and well-formed obligations, and much more. But the general principle is the same.
Related
In the following example code the trait Foo requires that the associated type X implement the Clone trait.
When using the impl Foo<X = Baz> syntax in the do_it function signature, cargo check does not complain that Baz does not implement the Clone trait.
However, cargo check does complain about this issue, in the impl Foo for Bar block.
I would have expected impl Foo<X = Baz> to complain in the same way.
trait Foo {
type X: Clone;
}
struct Bar;
struct Baz;
impl Foo for Bar {
type X = Baz; // <- complains Baz does not impl Clone trait
}
fn do_it(foo: impl Foo<X = Baz>) {} // <- does not complain
This is not the case if X is a generic parameter. In that case, cargo check indicates that the Clone trait bound is not satisfied by foo: impl Foo<Bar>
trait Foo<X>
where
X: Clone,
{
}
struct Bar;
struct Baz;
fn do_it(foo: impl Foo<Baz>) {} // <- complains Baz does not impl Clone trait
Is this intended behavior and if so why?
This is described in the RFC introducing associated types, in a short sentence:
The BOUNDS and WHERE_CLAUSE on associated types are obligations for the implementor of the trait, and assumptions for users of the trait:
trait Graph {
type N: Show + Hash;
type E: Show + Hash;
...
}
impl Graph for MyGraph {
// Both MyNode and MyEdge must implement Show and Hash
type N = MyNode;
type E = MyEdge;
...
}
fn print_nodes<G: Graph>(g: &G) {
// here, can assume G::N implements Show
...
}
What this means is that the person that is responsible to prove that the bounds hold is not the user of the trait (do_it() in your example) but the implementor of the trait. This is in contrast to generic parameters of traits, where the proof obligation is on the user.
The difference should be obvious when you look at it: with generic parameters, the types are foreign and unknown inside the trait implementation, so it must assume the bounds hole. The user of the trait, on the other hand, has concrete types for them (even if they're themselves generic, they're still concrete types from the point of view of the trait) and so it should prove the bounds hold. In contrast, with associated types the story is different: the implementor knows the concrete type, while the user assumes a generic type (even if, like in your code, it constrains them to a specific type, in the general case it is still unknown).
Note that with where bounds on associated types (type Foo where Self::Foo: Clone), that were introduced with generic associated types (yes, I know the RFC I linked bring them, but as far as I know they were not implemented and eventually implemented as part of GATs with different semantics), the story is again different from normal associated type bounds: the user has to prove them too (I think the both need to prove, but I'm not sure). This is because they're expected to be used for generic parameters on associated types, so they're similar to generic parameters on traits, or where clauses in them.
I'm having trouble understanding how values of boxed traits come into existence. Consider the following code:
trait Fooer {
fn foo(&self);
}
impl Fooer for i32 {
fn foo(&self) { println!("Fooer on i32!"); }
}
fn main() {
let a = Box::new(32); // works, creates a Box<i32>
let b = Box::<i32>::new(32); // works, creates a Box<i32>
let c = Box::<dyn Fooer>::new(32); // doesn't work
let d: Box<dyn Fooer> = Box::new(32); // works, creates a Box<Fooer>
let e: Box<dyn Fooer> = Box::<i32>::new(32); // works, creates a Box<Fooer>
}
Obviously, variant a and b work, trivially. However, variant c does not, probably because the new function takes only values of the same type which is not the case since Fooer != i32. Variant d and e work, which lets me suspect that some kind of automatic conversion from Box<i32> to Box<dyn Fooer> is being performed.
So my questions are:
Does some kind of conversion happen here?
If so, what the mechanism behind it and how does it work? (I'm also interested in the low level details, i.e. how stuff is represented under the hood)
Is there a way to create a Box<dyn Fooer> directly from an i32? If not: why not?
However, variant c does not, probably because the new function takes only values of the same type which is not the case since Fooer != i32.
No, it's because there is no new function for Box<dyn Fooer>. In the documentation:
impl<T> Box<T>
pub fn new(x: T) -> Box<T>
Most methods on Box<T> allow T: ?Sized, but new is defined in an impl without a T: ?Sized bound. That means you can only call Box::<T>::new when T is a type with a known size. dyn Fooer is unsized, so there simply isn't a new function to call.
In fact, that function can't exist in today's Rust. Box<T>::new needs to know the concrete type T so that it can allocate memory of the right size and alignment. Therefore, you can't erase T before you send it to Box::new. (It's conceivable that future language extensions may allow functions to accept unsized parameters; however, it's unclear whether even unsized_locals would actually enable Box<T>::new to accept unsized T.)
For the time being, unsized types like dyn Fooer can only exist behind a "fat pointer", that is, a pointer to the object and a pointer to the implementation of Fooer for that object. How do you get a fat pointer? You start with a thin pointer and coerce it. That's what's happening in these two lines:
let d: Box<Fooer> = Box::new(32); // works, creates a Box<Fooer>
let e: Box<Fooer> = Box::<i32>::new(32); // works, creates a Box<Fooer>
Box::new returns a Box<i32>, which is then coerced to Box<Fooer>. You could consider this a conversion, but the Box isn't changed; all the compiler does is stick an extra pointer on it and forget its original type. rodrigo's answer goes into more detail about the language-level mechanics of this coercion.
Hopefully all of this goes to explain why the answer to
Is there a way to create a Box<Fooer> directly from an i32?
is "no": the i32 has to be boxed before you can erase its type. It's the same reason you can't write let x: Fooer = 10i32.
Related
Why can't I write a function with the same type as Box::new?
Are polymorphic variables allowed?
How do you actually use dynamically sized types in Rust?
Why is `let ref a: Trait = Struct` forbidden?
I'll try to explain what conversions (coercions) happen in your code.
There is a marker trait named Unsize that, between others:
Unsize is implemented for:
T is Unsize<Trait> when T: Trait.
[...]
This trait, AFAIK, is not used directly for coercions. Instead, CoerceUnsized is used. This trait is implemented in a lot of cases, some of them are quite expected, such as:
impl<'a, 'b, T, U> CoerceUnsized<&'a U> for &'b T
where
'b: 'a,
T: Unsize<U> + ?Sized,
U: ?Sized
that is used to coerce &i32 into &Fooer.
The interesting, not so obvious implementation for this trait, that affects your code is:
impl<T, U> CoerceUnsized<Box<U>> for Box<T>
where
T: Unsize<U> + ?Sized,
U: ?Sized
This, together with the definition of the Unsize marker, can be somewhat read as: if U is a trait and T implements U, then Box<T> can be coerced into Box<U>.
About your last question:
Is there a way to create a Box<Fooer> directly from an i32? If not: why not?
Not that I know of. The problem is that Box::new(T) requires a sized value, since the value passed is moved into the box, and unsized values cannot be moved.
In my opinion, the easiest way to do that is to simply write:
let c = Box::new(42) as Box<Fooer>;
That is, you create a Box of the proper type and then coerce to the unsized one (note it looks quite similar to your d example).
I'm having trouble understanding how values of boxed traits come into existence. Consider the following code:
trait Fooer {
fn foo(&self);
}
impl Fooer for i32 {
fn foo(&self) { println!("Fooer on i32!"); }
}
fn main() {
let a = Box::new(32); // works, creates a Box<i32>
let b = Box::<i32>::new(32); // works, creates a Box<i32>
let c = Box::<dyn Fooer>::new(32); // doesn't work
let d: Box<dyn Fooer> = Box::new(32); // works, creates a Box<Fooer>
let e: Box<dyn Fooer> = Box::<i32>::new(32); // works, creates a Box<Fooer>
}
Obviously, variant a and b work, trivially. However, variant c does not, probably because the new function takes only values of the same type which is not the case since Fooer != i32. Variant d and e work, which lets me suspect that some kind of automatic conversion from Box<i32> to Box<dyn Fooer> is being performed.
So my questions are:
Does some kind of conversion happen here?
If so, what the mechanism behind it and how does it work? (I'm also interested in the low level details, i.e. how stuff is represented under the hood)
Is there a way to create a Box<dyn Fooer> directly from an i32? If not: why not?
However, variant c does not, probably because the new function takes only values of the same type which is not the case since Fooer != i32.
No, it's because there is no new function for Box<dyn Fooer>. In the documentation:
impl<T> Box<T>
pub fn new(x: T) -> Box<T>
Most methods on Box<T> allow T: ?Sized, but new is defined in an impl without a T: ?Sized bound. That means you can only call Box::<T>::new when T is a type with a known size. dyn Fooer is unsized, so there simply isn't a new function to call.
In fact, that function can't exist in today's Rust. Box<T>::new needs to know the concrete type T so that it can allocate memory of the right size and alignment. Therefore, you can't erase T before you send it to Box::new. (It's conceivable that future language extensions may allow functions to accept unsized parameters; however, it's unclear whether even unsized_locals would actually enable Box<T>::new to accept unsized T.)
For the time being, unsized types like dyn Fooer can only exist behind a "fat pointer", that is, a pointer to the object and a pointer to the implementation of Fooer for that object. How do you get a fat pointer? You start with a thin pointer and coerce it. That's what's happening in these two lines:
let d: Box<Fooer> = Box::new(32); // works, creates a Box<Fooer>
let e: Box<Fooer> = Box::<i32>::new(32); // works, creates a Box<Fooer>
Box::new returns a Box<i32>, which is then coerced to Box<Fooer>. You could consider this a conversion, but the Box isn't changed; all the compiler does is stick an extra pointer on it and forget its original type. rodrigo's answer goes into more detail about the language-level mechanics of this coercion.
Hopefully all of this goes to explain why the answer to
Is there a way to create a Box<Fooer> directly from an i32?
is "no": the i32 has to be boxed before you can erase its type. It's the same reason you can't write let x: Fooer = 10i32.
Related
Why can't I write a function with the same type as Box::new?
Are polymorphic variables allowed?
How do you actually use dynamically sized types in Rust?
Why is `let ref a: Trait = Struct` forbidden?
I'll try to explain what conversions (coercions) happen in your code.
There is a marker trait named Unsize that, between others:
Unsize is implemented for:
T is Unsize<Trait> when T: Trait.
[...]
This trait, AFAIK, is not used directly for coercions. Instead, CoerceUnsized is used. This trait is implemented in a lot of cases, some of them are quite expected, such as:
impl<'a, 'b, T, U> CoerceUnsized<&'a U> for &'b T
where
'b: 'a,
T: Unsize<U> + ?Sized,
U: ?Sized
that is used to coerce &i32 into &Fooer.
The interesting, not so obvious implementation for this trait, that affects your code is:
impl<T, U> CoerceUnsized<Box<U>> for Box<T>
where
T: Unsize<U> + ?Sized,
U: ?Sized
This, together with the definition of the Unsize marker, can be somewhat read as: if U is a trait and T implements U, then Box<T> can be coerced into Box<U>.
About your last question:
Is there a way to create a Box<Fooer> directly from an i32? If not: why not?
Not that I know of. The problem is that Box::new(T) requires a sized value, since the value passed is moved into the box, and unsized values cannot be moved.
In my opinion, the easiest way to do that is to simply write:
let c = Box::new(42) as Box<Fooer>;
That is, you create a Box of the proper type and then coerce to the unsized one (note it looks quite similar to your d example).
I have the module mod1.rs:
pub struct Foo;
impl Foo {}
impl Drop for Foo {
fn drop(&mut self) {}
}
In file2.rs I wrote use mod1::Foo;.
What do I actually have in file2.rs? Only struct Foo, impl Foo? What about impl Drop for Foo?
If I get all traits for Foo in file2.rs, and I write
fn my_func(foo: Foo)..., what do I have here? Is Foo a struct or a trait (impl Foo) here?
I read the Rust book and manual, but they explain only
explicit usage, not mention what happens with trait with the same name (impl). The Rust book tells you to import traits explicitly, if so and Drop is not imported by use mod1::Foo, this is a really, really bad thing.
In file2.rs I wrote use mod1::Foo;.
What do I actually have in file2.rs? Only struct Foo, impl Foo? What about impl Drop for Foo?
When you use a type like a struct or an enum, you get all of the inherent methods; those defined in the impl Foo. You'd also be able to access any public fields on the type.
If I get all traits for Foo in file2.rs, and I write fn my_func(foo: Foo), what do I have here? Is Foo a struct or a trait (impl Foo) here?
impl Foo is not a trait. trait Bar defines a trait. impl Bar for Foo implements a trait for the type Foo. impl Foo creates inherent methods; these are not related to traits.
I read the Rust book and manual, but they explain only explicit usage, not mention what happens with trait with the same name (impl). The Rust book tells you to import traits explicitly, if so and Drop is not imported by use mod1::Foo, this is a really, really bad thing.
That would be a very bad idea for the language designers to have made. Thankfully, they didn't do that. Importing something simply allows the code that imported it to use it. It doesn't cause the code to disappear if it's not imported.
The compiler itself is the user of types that implement Drop, so you can think of it as the compiler implementation has use Drop in it somewhere. This is probably not literally true, but a mental model. Just because your code doesn't import Drop doesn't mean some other code couldn't.
As mentioned elsewhere, you don't have to import Drop anyway, as it's included in the prelude.
I have a trait Foo inheriting from another trait Bar. Bar has an associated type Baz. Foo constrains Baz such that Baz must implement Hoge.
trait Hoge {}
trait Bar {
type Baz;
}
trait Foo: Bar where Self::Baz: Hoge {}
However, when I define a generic function requiring the generic type T to implement Foo,
// [DESIRED CODE]
fn fizz<T: Foo>(buzz: T) {
// ...
}
rustc complains with EO277 unless I constrain T explicitly:
fn fizz<T: Foo>(buzz: T) where T::Baz: Hoge {
// ...
}
I do not understand why I need to do this. I would like to be able to write [DESIRED CODE]. What is the recommended way to do this?
Sadly (or not), you have to repeat the bounds.
Last year I opened a issue thinking that the type checker was being inconsistent. The code is similar to yours.
#arielb1 closed the issue and said that this was the intended behavior and gave this explanation:
The thing is that we don't want too many bounds to be implicitly
available for functions, as this can lead to fragility with distant
changes causing functions to stop compiling. There are basically 3
kinds of bounds available to a function:
bounds from explicit where-clauses - e.g. T: B when you have that clause. This includes the "semi-explicit" Sized bound.
bounds from supertraits of explicit where-clauses - a where-clause adds bounds for its supertraits (as trait B: A, the T: B bound adds a
T: A bound).
bounds from the lifetime properties of arguments (outlives/implicator/implied bounds). These are only lifetime bounds,
and irrelevant for the current problem. rust-lang/rfcs#1214 involved
them a great deal.
If your bound isn't in the list, you will have to add it explicitly if
you want to use it. I guess this should be a FAQ entry.
Today I opened an issue to request that this information to be added to the docs.
It is possible to work around this behaviour by using another associated type in Foo since the compiler accepts implicit bounds when part of the associated type definition (playground):
trait Foo: Bar<Baz = Self::HogeBaz> {
type HogeBaz: Hoge;
}
The new associated type can be hidden in a helper trait to avoid having to include it in every implementation. Full example (with renaming for clarity)
(playground)
trait Bound {
fn bound();
}
trait Trait {
type Type;
}
trait BoundedTypeHelper: Trait<Type = Self::BoundedType> {
type BoundedType: Bound;
}
impl<T> BoundedTypeHelper for T
where
T: Trait,
Self::Type: Bound,
{
type BoundedType = Self::Type;
}
trait UserTrait: BoundedTypeHelper {}
fn fizz<T: UserTrait>() {
T::Type::bound()
}
I am with you in thinking that the original where-based bound ought to be treated as part of the trait definition and applied implicitly. It feels very arbitrary that bounding associated types inline works but where clauses do not.