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 f31265fb..15a4fec1 100644 --- a/src/trait-bounds.md +++ b/src/trait-bounds.md @@ -44,68 +44,89 @@ certain common cases: `trait A { type B: Copy; }` is equivalent to `trait A where Self::B: Copy { type B; }`. +r[bound.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. + 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. + +r[bound.satisfaction.impl] + +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. + +r[bound.satisfaction.impl.builtin] + +There exist impls which are automatically generated by the compiler. + +- `Sized`,`Copy`, `Clone`,... + + +- alternative: mention this in item-kind impl + +r[bound.satisfaction.impl.builtin.trait-object] + +Trait objects implement their trait if TODO: lookup conditions, something something project bounds make sense + +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. + +r[bound.satisfaction.alias-bounds] + +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 -# type Surface = i32; -trait Shape { - fn draw(&self, surface: Surface); - fn name() -> &'static str; +trait Trait { + type Assoc: Clone; } -fn draw_twice(surface: Surface, sh: T) { - sh.draw(surface); // Can call method because T: Shape - sh.draw(surface); +fn foo(x: &T::Assoc) -> T::Assoc { + // The where-bound `T::Assoc: Clone` is satisfied using the `Clone` item-bound. + x.clone() } +``` -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 -} +r[bound.satisfaction.alias-bounds.nested] -struct Figure(S, S); +We also consider the item bounds of the self type of aliases to satisfy trait bounds. -fn name_figure( - figure: Figure, // Type Figure is well-formed because U: Shape -) { - println!( - "Figure of two {}", - U::name(), // Can use associated function - ); +```rust +trait Trait { + type Assoc: Iterator + where + ::Item: Clone; + // equivalent to + // type Assoc: Iterator; } -``` -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, +fn item_is_clone(iter: T::Assoc) { + for item in iter { + let _ = item.clone(); + } } -struct UsesA<'a, T>(A<'a, T>); ``` + +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. + +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. ... + 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..9e497b7f 100644 --- a/src/types.md +++ b/src/types.md @@ -150,6 +150,31 @@ enum List { 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, +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. + +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] + +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 diff --git a/src/types/alias-types.md b/src/types/alias-types.md new file mode 100644 index 00000000..d10ab761 --- /dev/null +++ b/src/types/alias-types.md @@ -0,0 +1,35 @@ +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 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] + +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