From 68681fcc047535ceb4ec730f81c04f842ee5e2f0 Mon Sep 17 00:00:00 2001 From: lcnr Date: Wed, 20 Aug 2025 16:03:11 +0200 Subject: [PATCH 1/4] cool beans --- src/trait-bounds.md | 103 +++++++++++++++++++-------------------- src/types.md | 11 +++++ src/types/alias-types.md | 34 +++++++++++++ 3 files changed, 95 insertions(+), 53 deletions(-) create mode 100644 src/types/alias-types.md diff --git a/src/trait-bounds.md b/src/trait-bounds.md index f31265fb..dad48487 100644 --- a/src/trait-bounds.md +++ b/src/trait-bounds.md @@ -44,67 +44,64 @@ certain common cases: `trait A { type B: Copy; }` is equivalent to `trait A where Self::B: Copy { type B; }`. +r[bound.global] + +A bound which does not use the item's parameters or any higher-ranked lifetimes are considered global. + +An error is emitted if a global bound cannot be satisfied in an empty environment. + r[bound.satisfaction] -Bounds on an item must be satisfied when using the item. When type checking and -borrow checking a generic item, the bounds can be used to determine that a -trait is implemented for a type. For example, given `Ty: Trait` -* In the body of a generic function, methods from `Trait` can be called on `Ty` - values. Likewise associated constants on the `Trait` can be used. -* Associated types from `Trait` can be used. -* Generic functions and types with a `T: Trait` bounds can be used with `Ty` - being used for `T`. +The bounds of an item must be satisfied when using that item. -```rust -# type Surface = i32; -trait Shape { - fn draw(&self, surface: Surface); - fn name() -> &'static str; -} +r[bound.satisfaction.impl] -fn draw_twice(surface: Surface, sh: T) { - sh.draw(surface); // Can call method because T: Shape - sh.draw(surface); -} +- a trait bound can be satisfied by using an implementation of that trait +- an impl is applicable if, after instantiating its generic parameters with inference variables + - TODO: how to talk about "instantiate with infer vars"... + - the self type and trait arguments are equal to the trait bound, + - the where-bounds of the impl can be recursively satisfied -fn copy_and_draw_twice(surface: Surface, sh: T) where T: Shape { - let shape_copy = sh; // doesn't move sh because T: Copy - draw_twice(surface, sh); // Can use generic function because T: Shape -} +[bound.satisfaction.impl.builtin] -struct Figure(S, S); +There exists impls which are automatically generated by the compiler. -fn name_figure( - figure: Figure, // Type Figure is well-formed because U: Shape -) { - println!( - "Figure of two {}", - U::name(), // Can use associated function - ); -} -``` +- `Sized`,`Copy`, `Clone`,... -r[bound.trivial] -Bounds that don't use the item's parameters or [higher-ranked lifetimes] are checked when the item is defined. -It is an error for such a bound to be false. - -r[bound.special] -[`Copy`], [`Clone`], and [`Sized`] bounds are also checked for certain generic types when using the item, even if the use does not provide a concrete type. -It is an error to have `Copy` or `Clone` as a bound on a mutable reference, [trait object], or [slice]. -It is an error to have `Sized` as a bound on a trait object or slice. - -```rust,compile_fail -struct A<'a, T> -where - i32: Default, // Allowed, but not useful - i32: Iterator, // Error: `i32` is not an iterator - &'a mut T: Copy, // (at use) Error: the trait bound is not satisfied - [T]: Sized, // (at use) Error: size cannot be known at compilation -{ - f: &'a T, -} -struct UsesA<'a, T>(A<'a, T>); -``` + +- alternative: mention this in item-kind impl + +[bound.satisfaction.impl.builtin.trait-object] + +Trait objects implement their trait if TODO: lookup conditions, something something project bounds make sense + +[bound.satisfaction.bounds] + +While inside of a generic item, trait bounds can be satisfied by using the where-bounds of the current item as the item is able to assume that its bounds are satisfied. For this, higher-ranked where-bounds can be instantiated with inference variables. The where-bound is then equated with the trait bound that needs to be satisfied. + +[bound.satisfaction.alias-bounds] + +- if an alias cannot be normalized in the current environment, trait bounds using this alias as a self type can be proven by using its item bounds +- TODO: alias bounds from nested self-types github.com/rust-lang/rust/pull/120752 + + +[bound.satisfaction.candidate-preference] + +> This is purely descriptive. Candidate preference behavior may change in future releases and must not be relied upon for correctness or soundness. + +If there are multiple ways to satisfy a trait bound, some groups of candidate are preferred over others. In case a single group has multiple different candidates, the bound remains ambiguous. Candidate preference has the following order +- builtin implementations of `Sized` +- if there are any non-global where-bounds, all where-bounds +- alias-bounds +- impls + - In case the goal trait bound does not contain any inference variables, we prefer builtin trait object impls over user-written impls. TODO: that's unsound jank +- global where-bounds (only relevant if it does not hold) + +> note: this candidate preference can result in incorrect errors and type mismatches, e.g. ... + +[bound.satisfaction.cycles] + +In case satisfying a bound requires recursively satisfying the same bound, we've encountered a *cycle*. TODO: terminology is iffy here .... can just drop this again r[bound.trait-object] Trait and lifetime bounds are also used to name [trait objects]. diff --git a/src/types.md b/src/types.md index 9c93f799..269bf0f0 100644 --- a/src/types.md +++ b/src/types.md @@ -150,6 +150,17 @@ enum List { let a: List = List::Cons(7, Box::new(List::Cons(13, Box::new(List::Nil)))); ``` +## Equality of types + +Equality and subtyping of types is generally structural. If the outermost type constructors are the same, +the fields are pairwise related. The only exceptions from this rule are higher ranked types and alias types + +higher ranked types: +uwu, instantiate binder with placeholders, eq + +aliases +normalized to a rigid type, then equated as normal + [Array]: types/array.md [Boolean]: types/boolean.md [Closures]: types/closure.md diff --git a/src/types/alias-types.md b/src/types/alias-types.md new file mode 100644 index 00000000..7985c157 --- /dev/null +++ b/src/types/alias-types.md @@ -0,0 +1,34 @@ +r[type.alias] + +- associated types +- opaque types + - link from "impl-trait type" to this or param + +r[type.alias.rigid] + +Aliases might be treated as rigid in their current environment. In this case they behave like other rigid types. + +r[type.alias.normalization] + +Alias types can be normalized to their underlying type. +- for associated types this is the type provided by the corresponding impl +- opaque types + +r[type.alias.normalization.assoc-type] + +Similar to how trait bounds get satisfied, associated types can be normalized via +multiple different candidates + +- impl (also builtin) +- projection bound in the environment TODO: where do we talk about them +- alias bound of their self type + +candidate preference: +- normalizing an alias relies on the candidate group used to prove their corresponding trait bound +- if corresponding trait bound has been proven via a where-bound or an alias-bound, we do not consider impls + - if there is no remaining candidate, the associated type is rigid + +For all applicable candidates we +- prefer where-bounds +- then alias bounds +- then impls From 943f00ca0cbe1b522598412e2470e27a03fdef5a Mon Sep 17 00:00:00 2001 From: lcnr Date: Wed, 27 Aug 2025 09:04:26 +0200 Subject: [PATCH 2/4] nyaa --- src/trait-bounds.md | 2 +- src/types.md | 23 ++++++++++++++++++----- src/types/alias-types.md | 3 ++- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/trait-bounds.md b/src/trait-bounds.md index dad48487..23845126 100644 --- a/src/trait-bounds.md +++ b/src/trait-bounds.md @@ -64,7 +64,7 @@ r[bound.satisfaction.impl] [bound.satisfaction.impl.builtin] -There exists impls which are automatically generated by the compiler. +There exist impls which are automatically generated by the compiler. - `Sized`,`Copy`, `Clone`,... diff --git a/src/types.md b/src/types.md index 269bf0f0..60ac4e74 100644 --- a/src/types.md +++ b/src/types.md @@ -152,14 +152,27 @@ let a: List = List::Cons(7, Box::new(List::Cons(13, Box::new(List::Nil)))); ## Equality of types +r[types.equality] + Equality and subtyping of types is generally structural. If the outermost type constructors are the same, -the fields are pairwise related. The only exceptions from this rule are higher ranked types and alias types +corresponding generic arguments are compared. The only exceptions from this rule are higher ranked types and alias types. + +r[types.equality.aliases] + +Aliases are compared by first normalizing them to a *rigid* type? and then equating their type constructors and recursing into their generic arguments. + +r[types.equality.higher-ranked] + +Function pointers and trait objects may be higher-ranked. + +r[types.equality.higher-ranked.sub] + +Subtyping is checked by instantiating the `for` of the subtype with inference variables and the `for` of the supertype with placeholders before relating them as normal. + +r[types.equality.higher-ranked.eq] -higher ranked types: -uwu, instantiate binder with placeholders, eq +Equality is checked by both instantiating the `for` of the lhs with inference variables and the `for` of the rhs with placeholders before equating them, and also doing the opposite. -aliases -normalized to a rigid type, then equated as normal [Array]: types/array.md [Boolean]: types/boolean.md diff --git a/src/types/alias-types.md b/src/types/alias-types.md index 7985c157..d10ab761 100644 --- a/src/types/alias-types.md +++ b/src/types/alias-types.md @@ -6,7 +6,8 @@ r[type.alias] r[type.alias.rigid] -Aliases might be treated as rigid in their current environment. In this case they behave like other rigid types. +Aliases might be treated as *rigid* in their current environment. In this case they behave like other types. +Their equality is structural, *rigid* aliases are only equal if both have the same type constructor and equal corresponding arguments. r[type.alias.normalization] From ec3a91d55797f17b06ffd01ed88ffd34cfb51a72 Mon Sep 17 00:00:00 2001 From: lcnr Date: Wed, 10 Sep 2025 16:19:35 +0200 Subject: [PATCH 3/4] Update src/trait-bounds.md --- src/trait-bounds.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/trait-bounds.md b/src/trait-bounds.md index 23845126..514fa3bd 100644 --- a/src/trait-bounds.md +++ b/src/trait-bounds.md @@ -46,7 +46,7 @@ certain common cases: r[bound.global] -A bound which does not use the item's parameters or any higher-ranked lifetimes are considered global. +Bounds which does not use the item's parameters or any higher-ranked lifetimes are considered global. An error is emitted if a global bound cannot be satisfied in an empty environment. From 7e3c7a8f362ca18362a7c3a813e14bcf0b5d27e7 Mon Sep 17 00:00:00 2001 From: lcnr Date: Wed, 17 Sep 2025 13:27:38 +0200 Subject: [PATCH 4/4] rarw --- src/items/generics.md | 6 +++++ src/trait-bounds.md | 56 ++++++++++++++++++++++++++++++------------- src/types.md | 9 +++---- 3 files changed, 51 insertions(+), 20 deletions(-) diff --git a/src/items/generics.md b/src/items/generics.md index cf560ac4..6a5f54a3 100644 --- a/src/items/generics.md +++ b/src/items/generics.md @@ -310,6 +310,12 @@ struct Foo<#[my_flexible_clone(unbounded)] H> { } ``` +r[items.generics.instantiation] +When using an item its generic parameters have to get instantiated. This replaces all occurances of the parameter with either the explicitly provided argument or a new unconstrained inference variable. + +Instantiating the generic parameters of an item generally requires proving its where clauses. + + [array repeat expression]: ../expressions/array-expr.md [arrays]: ../types/array.md [slices]: ../types/slice.md diff --git a/src/trait-bounds.md b/src/trait-bounds.md index 514fa3bd..15a4fec1 100644 --- a/src/trait-bounds.md +++ b/src/trait-bounds.md @@ -56,13 +56,11 @@ The bounds of an item must be satisfied when using that item. r[bound.satisfaction.impl] -- a trait bound can be satisfied by using an implementation of that trait -- an impl is applicable if, after instantiating its generic parameters with inference variables - - TODO: how to talk about "instantiate with infer vars"... - - the self type and trait arguments are equal to the trait bound, - - the where-bounds of the impl can be recursively satisfied +A trait bound can be satisfied by using an implementation of that trait. An implementation is applicable if, +after instantiating its generic parameters with new inference variables, the self type and trait arguments are +equal to the trait bound and the where-bounds of the impl can be recursively satisfied. -[bound.satisfaction.impl.builtin] +r[bound.satisfaction.impl.builtin] There exist impls which are automatically generated by the compiler. @@ -71,21 +69,51 @@ There exist impls which are automatically generated by the compiler. - alternative: mention this in item-kind impl -[bound.satisfaction.impl.builtin.trait-object] +r[bound.satisfaction.impl.builtin.trait-object] Trait objects implement their trait if TODO: lookup conditions, something something project bounds make sense -[bound.satisfaction.bounds] +r[bound.satisfaction.bounds] While inside of a generic item, trait bounds can be satisfied by using the where-bounds of the current item as the item is able to assume that its bounds are satisfied. For this, higher-ranked where-bounds can be instantiated with inference variables. The where-bound is then equated with the trait bound that needs to be satisfied. -[bound.satisfaction.alias-bounds] +r[bound.satisfaction.alias-bounds] -- if an alias cannot be normalized in the current environment, trait bounds using this alias as a self type can be proven by using its item bounds -- TODO: alias bounds from nested self-types github.com/rust-lang/rust/pull/120752 +If an alias type is rigid in the current environment, trait bounds using this alias as a self type can be satisfied by using its item bounds. + +```rust +trait Trait { + type Assoc: Clone; +} + +fn foo(x: &T::Assoc) -> T::Assoc { + // The where-bound `T::Assoc: Clone` is satisfied using the `Clone` item-bound. + x.clone() +} +``` + +r[bound.satisfaction.alias-bounds.nested] + +We also consider the item bounds of the self type of aliases to satisfy trait bounds. + +```rust +trait Trait { + type Assoc: Iterator + where + ::Item: Clone; + // equivalent to + // type Assoc: Iterator; +} + +fn item_is_clone(iter: T::Assoc) { + for item in iter { + let _ = item.clone(); + } +} +``` -[bound.satisfaction.candidate-preference] +r[bound.satisfaction.candidate-preference] > This is purely descriptive. Candidate preference behavior may change in future releases and must not be relied upon for correctness or soundness. @@ -99,10 +127,6 @@ If there are multiple ways to satisfy a trait bound, some groups of candidate ar > note: this candidate preference can result in incorrect errors and type mismatches, e.g. ... -[bound.satisfaction.cycles] - -In case satisfying a bound requires recursively satisfying the same bound, we've encountered a *cycle*. TODO: terminology is iffy here .... can just drop this again - r[bound.trait-object] Trait and lifetime bounds are also used to name [trait objects]. diff --git a/src/types.md b/src/types.md index 60ac4e74..9e497b7f 100644 --- a/src/types.md +++ b/src/types.md @@ -154,12 +154,14 @@ let a: List = List::Cons(7, Box::new(List::Cons(13, Box::new(List::Nil)))); r[types.equality] -Equality and subtyping of types is generally structural. If the outermost type constructors are the same, -corresponding generic arguments are compared. The only exceptions from this rule are higher ranked types and alias types. +Equality and subtyping of types is generally structural; if the outermost type constructors are the same, +their corresponding generic arguments are pairwise compared. We say types with this equality behavior are *rigid*. The only exceptions from this rule are higher ranked types and alias types. + +r[types.equality.rigid] r[types.equality.aliases] -Aliases are compared by first normalizing them to a *rigid* type? and then equating their type constructors and recursing into their generic arguments. +Aliases are compared by first normalizing them to a *rigid* type and then equating their type constructors and recursing into their generic arguments. r[types.equality.higher-ranked] @@ -173,7 +175,6 @@ r[types.equality.higher-ranked.eq] Equality is checked by both instantiating the `for` of the lhs with inference variables and the `for` of the rhs with placeholders before equating them, and also doing the opposite. - [Array]: types/array.md [Boolean]: types/boolean.md [Closures]: types/closure.md