Where clause requires too much information

You can use the where clause with complicated constraints like where Option<T>: Default instead of just T: Default, but is there a reason why it shouldn't be possible to do something like the following example:

#[derive(Default)]
struct Test<A: Default, B: Default, C: Default> {
    first: A,
    second: B,
    third: C,
}

impl<A, B, C> Test<A, B, C> where 
    (A, B, C): Default
{
    // something
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0277]: the trait bound `A: std::default::Default` is not satisfied
 --> src/lib.rs:8:15
  |
2 | struct Test<A: Default, B: Default, C: Default> {
  |                ------- required by this bound in `Test`
...
8 | impl<A, B, C> Test<A, B, C> where 
  |               ^^^^^^^^^^^^^ the trait `std::default::Default` is not implemented for `A`
  |
help: consider further restricting type parameter `A`
  |
9 |     (A, B, C): Default, A: std::default::Default
  |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0277]: the trait bound `B: std::default::Default` is not satisfied
 --> src/lib.rs:8:15
  |
2 | struct Test<A: Default, B: Default, C: Default> {
  |                            ------- required by this bound in `Test`
...
8 | impl<A, B, C> Test<A, B, C> where 
  |               ^^^^^^^^^^^^^ the trait `std::default::Default` is not implemented for `B`
  |
help: consider further restricting type parameter `B`
  |
9 |     (A, B, C): Default, B: std::default::Default
  |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0277]: the trait bound `C: std::default::Default` is not satisfied
 --> src/lib.rs:8:15
  |
2 | struct Test<A: Default, B: Default, C: Default> {
  |                                        ------- required by this bound in `Test`
...
8 | impl<A, B, C> Test<A, B, C> where 
  |               ^^^^^^^^^^^^^ the trait `std::default::Default` is not implemented for `C`
  |
help: consider further restricting type parameter `C`
  |
9 |     (A, B, C): Default, C: std::default::Default
  |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to 3 previous errors

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

In the end, the only thing that would compile was this:

impl<A, B, C> Test<A, B, C> where 
    A: Default,
    B: Default, 
    C: Default
{ }

Is there a way to achieve this without having to write out the constraints in every impl?

1 Like

Well, think about it this way:
In the structure definition you say "A,B,C must all fulfill the Default trait".
In the impl block you say " (A,B,C) must implement the default trait ".
Now, since (A,B,C) is a type on its own and just because it is composed of the types A,B,C does not mean A,B,C implement the same traits as it does.

What I read, typically you don't want trait bounds on structs, but only on the implementations that require them.

4 Likes

It's like @raidwas said, plus you can have multiple impl blocks for a type. So you can optionally implement methods or other traits depending on what the generic parameters implement. I believe this is used extensively in the standard library.

Option from the standard library is a great example. I've linked to the section on trait implementations and you can see that Option<T> only implements Clone or Copy if T does. The same holds for Result, where Clone and Copy are only implemented for Result<T, E> if both T and E implement them.

1 Like

In general I agree. One exception I've recently run into is if you want to use an associated type of a trait in a struct or enum definition. But it's a fairly niche pattern in that not much code in the wild seems to actually do that at this time.

That makes sense, and I habe to agree that it sounds like a niche pattern. Maybe it would be nice to have a collection of situations where it makes sense to have bounds on the struct? Because when I first read the book (quite some time ago, maybe things changed) I was a bit confused why those existed (and just assumed they had some purpose, even if not clear to me at that point).

Here's a struct with bounds, where it should be obvious why they are there:

pub struct TineTree<T>(BTreeSet<Tine<T>>) where T: Ord + Clone;

I wouldn't count it as obvious, as BTreeSet does not impose those bounds on the strict itself (only on the impl blocks).
And that is exactly the thing: In many cases the bounds are not necessary on the struct.

(If Tine has those bounds on the struct I may count Tine as an example, but only if it is really necessary for it to have the bounds on the struct)

You might be right, I was assuming the requirement came from BTreeSet, but it may actually be coming from a derive attribute...

But ultimately the T: Ord comes from the BTreeSet because insertion in a BTree requires an ordering relationship

1 Like

You still wouldn't want the bound on the actual struct if you can help it, in case you need to create it in a generic context without an Ord constraint. For instance, if you're defining a data structure and you want to use a BTree to hold optional data in the case Ord is satisfied, but omit those features when it is not. You'd still need to be able to create an empty BTreeSet even if you can't do anything else with it.

My TineTree example is misleading, because I had #[derive(Default)] on it, and BTreeSet's Default impl has those bounds. Thus I can get rid of them by pushing them onto my own Default impl.

This is interesting and I think makes a lot of sense. Do you have any links to resources you've seen?

I was worried that it wouldn't be obvious to consumers of the API that they'd need to use a certain bound, but the rust compiler comes to the rescue!

struct Test<A> (A);

impl<A> Test<A> where A: From<f32> {
    fn update(&mut self, value: A) {
        self.0 = value.into();
    }
}

fn test() {
    let test: Test<()> = todo!();
    test.update(10.);
}

(Playground)

error[E0599]: no method named `update` found for struct `Test<()>` in the current scope
  --> src/lib.rs:11:10
   |
1  | struct Test<A> (A);
   | ------------------- method `update` not found for this
...
11 |     test.update(10.);
   |          ^^^^^^ method not found in `Test<()>`
   |
   = note: the method `update` exists but the following trait bounds were not satisfied:
           `(): std::convert::From<f32>`

Not directly. I came to that conclusion after reading the implementations from some types in the std (to be more precise: until this day I haven't come across a struct in the std that uses bounds. Although I didn't explicitly search for it either)

1 Like

I thought about your example a bit and wondered:
Do you actually require the bound on the struct? The struct itself could have a generic parameter and the implementations could bound the parameter with the associated type, right?

I think I will open a new thread focusing on this question, or maybe a mod could move some of the comments to a new thread? (I would argue the discussion does not revolve around ops question anymore)

Yes, it's required. Consider this snippet:


trait Foo {
    type Assoc: Debug;
} 

struct Bar<T: Foo> {
    field: <T as Foo>::Assoc
} 

Without the trait bound, there would be no way to express that field has a type that is related to T by means of its associated type. No amount of massaging impl blocks will change that.

This is the pattern I was talking about earlier.

1 Like

I understood what you meant, but I thought it could be written as follows:

trait Foo {
    type Assoc: core::fmt::Debug;
} 

struct Bar<T> {
    field: T
}

impl<F: Foo> Bar<<F as Foo>::Assoc>{
    //stuff
}

But it turns out this does not compile since the generic parameter F in the impl block is not constraint by any type.

That will not compile, because if two different types have the same Assoc, then Bar<ThatAssocType> would be implemented twice, which is not allowed.

1 Like

I think I found a way (not saying its as nice a solution as your way):

trait Foo {
    type Assoc: core::fmt::Debug;
} 

struct Bar<F,T> {
    field: T,
    phantom: core::marker::PhantomData<F>,
}

impl<F: Foo> Bar<F, <F as Foo>::Assoc>{
    
}

The PhantomData basically just solves the problem @alice mentioned.

I came across one case where it's required by the compiler and was reminded of this post:

trait Foo {}

struct MyStruct<T> {
    t: T
}

impl<T: Foo> Drop for MyStruct<T> {
    fn drop(&mut self) {}
}

error[E0367]: `Drop` impl requires `T: SomeTrait` but the struct it is implemented for does not

rustc --explain E0367
An attempt was made to implement `Drop` on a specialization of a generic type.
...
This code is not legal: it is not possible to specialize `Drop` to a subset of
implementations of a generic type. In order for this code to work, `MyStruct`
must also require that `T` implements `Foo`.
3 Likes

Thats interesting. I assume that is because it wouldn't be clear which drop implementation to use (as their would be a generic one and a "specialized" one).
I wonder if this limitation could be removed once we have specialization?

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.