Expand description
§::implied-bounds
Trait trick and helper convenience macro to make all of the bounds of a trait definition be properly implied/entailed, as expected, avoiding “non-entailment” bugs which the current trait system has.
§Prior Context
Click to show
In Rust, when defining a trait
or some helper type, such as a struct
, certain bounds are
not implied/entailed, which is probably contrary to the user/human expectation, and thus,
surprising, unintuitive, and being honest, annoying, since it then requires the user of such trait
and struct
to be repeating the bounds.
struct Typical<T: Clone>(T);
fn demo<T>(_: Typical<T>) {} // Error, missing `T: Clone`…
-
Error message:
Click to show
ⓘerror[E0277]: the trait bound `T: Clone` is not satisfied --> src/lib.rs:3:15 | 3 | fn demo<T>(_: Typical<T>) {} | ^^^^^^^^^^ the trait `Clone` is not implemented for `T` | note: required by a bound in `Typical` --> src/lib.rs:1:19 | 1 | struct Typical<T: Clone>(T); | ^^^^^ required by this bound in `Typical` help: consider restricting type parameter `T` with trait `Clone` | 3 | fn demo<T: std::clone::Clone>(_: Typical<T>) {} | +++++++++++++++++++
And likewise for a trait
:
trait MyTrait<U: Clone> {}
fn demo<T: MyTrait<U>, U>() {} // Error, missing `U: Clone`…
-
Error message:
Click to show
ⓘerror[E0277]: the trait bound `U: Clone` is not satisfied --> src/lib.rs:3:12 | 3 | fn demo<T: MyTrait<U>, U>() {} | ^^^^^^^^^^ the trait `Clone` is not implemented for `U` | note: required by a bound in `MyTrait` --> src/lib.rs:1:18 | 1 | trait MyTrait<U: Clone> {} | ^^^^^ required by this bound in `MyTrait` help: consider restricting type parameter `U` with trait `Clone` | 3 | fn demo<T: MyTrait<U>, U: std::clone::Clone>() {} | +++++++++++++++++++
Enter this crate.
§What this crate offers
are tools to alleviate the trait
case (currently, there is no special tooling for the struct
case, but some may be added in the future).
Mainly:
-
the maximally convenient
#[implied_bounds]
attribute:#[::implied_bounds::implied_bounds] // 👈 trait MyTrait<U: Clone> {} fn demo<T: MyTrait<U>, U>() {} // ✅
-
Otherwise, if you are wary of magical macros and prefer magical type-system stuff (🤷), if anything, because they do not impact from-scratch compile-time, you can directly use
ImpliedPredicate
(which is the helper trait used by the macro, under the hood, in its expansion).// no longer needed, since it's implied by the added clause // (but it can generally still be kept if you so wish) // vvvvvvvvvvvv trait MyTrait<U /*: Clone */> : ::implied_bounds::ImpliedPredicate<U, Impls: Clone> + // 👈 {} fn demo<T: MyTrait<U>, U>() {} // ✅
-
Note: you can disable the macro API —and thus, its compilation— by disabling the otherwise-enabled-by-
"default"
"proc-macros"
Cargo feature.Do note, however, that:
-
the macro involves a couple of extra knowledge-savy heuristics so as to maximize the quality of the diagnostics should implementors fail to abide by the clauses of trait;
-
the compile-time impact of
syn
with"full"
features (the only noticeable thing) is:-
of about 1 full second, tops;
-
only encountered when compiling from scratch, since it’s otherwise cached;
-
and only avoidable if you curate every transitive dependency of your dependency tree to avoid it, even though inevitably some crate somewhere shall pull it in, eventually.
Let he who is without
syn
cast the firstbuild
.
-
-
-
§Which predicates are currently not implied/entailed?
Click to show
-
Bounds on generic parameters:
trait Example<U: Clone> {}
-
where
clauses wherein the left hand side (“bounded type”) of the predicate is neitherSelf
nor a simple associated type:trait Example where Self : Sized, // entailed, since equivalent to `trait Example : Sized {`, Self::SimpleAssoc : Send, // entailed, since equivalent to `type SimpleAssoc : Send` // But none of the following clauses are entailed/implied: String : Into<Self>, for<'r> &'r Self : IntoIterator, Self::Gat<true> : Send, { type SimpleAssoc; type Gat<const IS_SEND: bool>; }
-
Note that that last
Self::Gat<true> : Send
clause can be rewritten as awhere
clause on the GAT itself:trait Example { type Gat<const IS_SEND: bool> where Self::Gat<true> : Send; }
This is both equivalent to the other syntax, and not detected/handled by the
#[implied_bounds]
attribute, so be aware you may need to hoist such clauses to the top-level/trait
-levelwhere
clauses in order for the attribute to pick them up and make them correctly implied/entailed.
-
§Inspiration / Credit be given where it is due
The ideas behind the design of the ImpliedPredicate
trait were brought to my attention courtesy
of the ::imply-hack
crate, from
@gtsiam
. Thank you!
I have gone with my own take on the matter, mainly:
-
to have the convenience macro;
-
to slightly rename the
Is
assoc type asImpls
, and pay special attention to the erroring diagnostics to make them as pretty as possible (mostly done in the macro).
Traits§
- Implied
Predicate - Helper trait for the implied/entailed bounds trick.
Attribute Macros§
- implied_
bounds default
orproc-macros
- Convenience attribute macro to help one rewrite a
trait
definition as per the rules described in the documentation ofImpliedPredicate
.