Addressing this point from brainstorming.md
- Fix contraints in type definitions. Remove the same quirk that Haskell used to have.
-
Adding special marker traits
InhabitedandContains<T>.Inhabitedallows for some forms of negative reasoning. -
If
S: Contains<T>thenS: InhabitedentailsT: InhabitedandT: !InhabitedentailsS: !Inhabited. -
We have e.g.
&'a T: Contains<T>,Containshat to be implemented manually but implementations are checked by the compiler for correctness. -
Also
!: !Inhabitedandstd::convert::Infallible: !Inhabited. -
Inhabitedcan’t be implemented manually for a type. Enums without variants are globally known to be!Inhabited. AlsoInhabited-implications analog to howContainsworks are considered between structs and public fields, or single-variant enums and their fields. This might include a rule such that all struct field being public and all fields beingInhabitedcan make the struct known to beInhabited. Also in general, if an (exhaustive) enum has an!Inhabitedfield in every variant, it’s!Inhabited; types that areContains-contained in some field of every variant areInhabitedif the enum isInhabited; and if all fields of some variant areInhabited, the enum isInhabited. -
Whenever a function has a parameter of some type
TypeExpr, inside of this function,TypeExpr: Inhabitedis known. The information thatT: Inhabitedholds can come up on a more fine-grained grained way than on the level of full function bodies. In the control flow, at any point where there is a value of typeTypeExpr,TypeExpr: Inhabitedhold true. Furthermore, if any control flow that leads to the current point is known to have come from a point where a value of typeTypeExprwas in scope, then stillTypeExpr: Inhabitedat that later point. Similarly if the value is not in scope has already been bound to a temporary in the current or previous statements, or if it could have been bound but was discarded, for example by a_in a pattern.- This last point aims to allow code like
The rule could be extended so that
let x: Result<i32, !> = Ok(42); match x { Ok(y) => y, Err(_) => {}, // block returns `!`, because `!: !Inhabited` // and the `!` could have been bound by the `_`
_ => {}works, too, and ultimately such that the whole branch is allowed to be omitted.
- This last point aims to allow code like
-
For a data type with constraints, like e.g.
struct HasEqConstraint<T: Eq>(PhantomData<*const T>);
using the constructor
HasEqConstraintrequiresT: Eqto be known. The same applies to unsized coercions, e.g. fromBox<HasEqConstraint<Struct>>toBox<HasEqConstraint<dyn Trait>>, assuming a version ofHasEqConstraintwith a?Sizedparameter in the first place. How unsized coercions interact with this in detail remains to be worked out fully.Then,
HasEqConstraint’s constraint makes the compiler aware thatHasEqConstraint<T>: InhabitedentailsT: Eq. -
Whenever at some point both
TypeExpr: InhabitedandTypeExpr: !Inhabitedholds, it is known that that point is unreachable. In particular, in unreachable code an end of a block has an implicit return value of!instead of(). -
The compiler has an internal, more precise notion of inhabited vs uninhabited types. Basically, this notion operates as if every legal
Containsimplementation would actually exist and all fields are public. This internal reasoning can be used to remove uninhabited variants from enums.