Pre-RFC: Allow where clauses on concrete types (solved: feature(trivial_bounds))

Summary

Allow the use of concrete types in where clause, regardless of whether they implement the traits or not.

Motivation

Regularity

As of today, it is possible to use concrete types in a where clause, but only if the predicate is satisfied.

That is, the following code compiles:

#[derive(Default)]
pub struct Foo(i64);

pub fn foo() -> Foo
where
    Foo: Default,
{
    Foo::default()
}

While commenting out the #[derive(Default)] leads to a compilation error:

error[E0277]: the trait bound `Foo: Default` is not satisfied
 --> src/lib.rs:6:5
  |
6 |     Foo: Default,
  |     ^^^^^^^^^^^^ the trait `Default` is not implemented for `Foo`
  |
  = help: see issue #48214
help: consider annotating `Foo` with `#[derive(Default)]`
  |
2 + #[derive(Default)]
3 | pub struct Foo(i64);
  |

Feature-less feature-detection

While exploring the solutions to rusts #2396, one elegant solution which popped in my mind was:

  1. Have a feature in the rustls crate, upon which a CryptoProvider would implement Default.
  2. Condition the presence of certain APIs on CryptoProvider implementing Default.

Now, within the crate, said APIs can easily be conditioned on the presence of the feature, obviously. The problem is that outside the crate, this feature cannot be checked, and therefore:

  • Each dependent crate needs its own matching feature.
  • Each user of those dependent crates need to activate all those matching features (if they use several independent crates).

This is a pain for both library writers and application writers.

Guide-level explanation

A where clause may be used to verify pre-conditions on any type:

  • Concrete types, such as String.
  • Concrete types with generic parameters, such as Vec<T, A>.
  • Generic parameters, such as T.

For example:

#[cfg_attr(feature = "default-crypto-provider", derive(Default))]
struct CryptoProvider { /**/ }

impl ClientConfig {
    /// Creates a builder for the ClientConfig.
    pub fn builder() -> ConfigBuilder<Self, WantsVersions>
    where
         CryptoProvider: Default
    {
        Self::builder_with_provider(CryptoProvider::default())
    }
}

Reference-level explanation

TBD

Drawbacks

  • Unclear how to difficult to implement it is.

Rationale and alternatives

Rationale

  • Regularity is good, it removes unexpected papercuts from the language.
  • The feature is genuinely useful, as motivated above.

Alternatives

  • Do nothing: let library authors and users get themselves tangled up in crate features. They love it, I swear!
  • Dependency Feature Testing: allow testing library features, ie #[cfg(feature = "rustls::default-crypto-provider")], which is independently useful. It's clunky, but at least it allows downstream libraries not to have to create their own features for it, and it means users don't have to enable the same feature 40 times in their Cargo.toml.

Prior art

I could not find any for this specific feature. The usefulness of where clause probably need no motivation of its own.

Unresolved questions

None, right now.

Future possibilities

Unclear whether there are more "non-allowed" pieces on the left hand of where clauses which could warrant looking into.

Perhaps you're looking for

I was indeed, and I didn't know it.

In particular, @cramertj saves the day as the feature isn't necessary as long as one adds a meaningless variable (playground):

pub struct Foo(i64);

pub fn foo() -> Foo
where
    for<'a> Foo: Default,
//  ^~~~~~~ Yes, it's weird! But it works! On STABLE!
{
    Foo::default()
}