Rust Types Team
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
      • Invitee
    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Sharing URL Help
Menu
Options
Versions and GitHub Sync Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
Invitee
Publish Note

Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

Your note will be visible on your profile and discoverable by anyone.
Your note is now live.
This note is visible on your profile and discoverable online.
Everyone on the web can find and read all notes of this public team.
See published notes
Unpublish note
Please check the box to agree to the Community Guidelines.
View profile
Engagement control
Commenting
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
  • Everyone
Suggest edit
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
Emoji Reply
Enable
Import from Dropbox Google Drive Gist Clipboard
   owned this note    owned this note      
Published Linked with GitHub
Subscribed
  • Any changes
    Be notified of any changes
  • Mention me
    Be notified of mention me
  • Unsubscribe
Subscribe
--- title: "T-types TAIT overview" tags: T-types, 2023-meetup-BRU, minutes date: 2023-10-10 / 2023-10-11 url: https://hackmd.io/QOsEaEJtQK-XDS_xN4UyQA --- # TAIT overview ## Old solver Inferring opaques in the old solver is mainly the responsibility of two parts of the type system: * Relations - `Eq`, `Sub`, `TypeRelating` (NLL) handle cases when opaques are equated with other types. * `replace_opaque_types_with_inference_vars`, which manually folds a type, replacing any in-scope opaques with inference variables, which is itself used in to places: * Checking a function body, replacing the TAITs and RPITs that show up in the signature with infer vars eagerly. This is necessary to handle fallback correctly. * In projection predicates. This is necessary to not break backwards-compatibility with nested RPITs that don't meet bounds. * [real world example in linkerd](https://github.com/rust-lang/rust/issues/99536) * [A minimal test using no nightly features](https://github.com/rust-lang/rust/blob/master/tests/ui/impl-trait/nested-return-type2.rs) Old solver (more notes): - Replace with infer vars for RPITs and TAITs in function signatures — main place both are constrained - For other cases, such as local variables and structs with TAIT return type, the eq and sub relations eagerly, depending on DefineOpaqueTypes - Bubble is too inaccurate so we need to *not* define types eagerly in some cases to avoid spurious errors. - Borrowck does not replace opaques eagerly with infer vars, and handles opaques in the relation - Opaques in queries act subtly different due to DefiningAnchor::Bubble - Projections are weird, can coincidentally replace opaques with infer vars outside of queries due to hack (link issue) ### `DefineOpaqueTypes` Since `Eq` and similar relations all succeed when comparing an opaque type against any type satisfying its bound, we sometimes run into issues (similar to how comparing against inference vars always succeeds). Contrary to its outdated documentation, it is not used just > to prevent predicate matching from matching anything against opaque types. But most usage sites (2/3rd) are now rejecting opaque types from being equal to any type fulfilling its bounds and registering said type as a hidden type. It is a feature grown out of necessity of how we do type checking and how opaque types in the old solver work. ```rust type Foo = impl Debug; fn test() { // case 1: let x: Foo = 22_i32; // Coercion from i32 to Foo? } ``` ```rust type Foo = impl Debug; fn test() { // case 2: let a: Option<_> = None; let x: Foo = a.unwrap(); // Coercion from ?X to Foo? } ``` ## Bubbles!! :left_speech_bubble: `DefiningAnchor::Bubble` -- * collect all the possible impact cases, re-apply at the call site, which is a bit risky * in case we drop those annotations on the floor * can define if things hold "modulo opaques" * can also impact the flow of trait resolution in "potentially unexpected" ways one challenging case: * opaque1 = opaque2, which is the hidden type of the other? * in bubble environment, we cannot know * we currently create a predicate and bubble that out ## Background - Don't want to go into much detail about how the `type_of` query actually delegates out to the `typeck` queries to find out where an opaque is defined. This doesn't change between the old solver, and is pretty well detailed [in the dev guide](https://rustc-dev-guide.rust-lang.org/opaque-types-impl-trait-inference.html#within-the-type_of-query). New solver: - Alias-relate predicate is responsible for all opaque eq ty, opaques are only constrained in obligations - Returns constrained opaques in query response ```rust type Foo = impl Sized; fn main() { let x: Foo = 22; println!("{x}"); // does this compile or not? Yes in new solver, no in old solver. let x: impl Debug = 22; // Does this just require Debug or is it like RPIT? println!("{x}"); } ``` ```rust fn tyeq<T>(_: T, _: T) {} fn main() { type Foo = impl Sized; let x: Foo = 42u32; let y: impl Sized = 42u32; tyeq(x, 0u32); // ? tyeq(y, 0u32); // ? } ``` `Self::Something = impl` ```rust impl X { type Something = impl Whatever; fn foo() { let x: Self::Something = ...; } } ``` Michael's point: - Given a type lambda application `Lambda<...Ti...>`, it should be valid to change to `?X = Ti` and `Lambda<...?x...>`. - Specific example: `T::Foo` normalization errs: Recursive calls act as real opaques, not replaced w/ infer vars ```rust fn foo() -> impl Debug { if something { foo() } else { 22 } } ``` ```rust fn foo() -> impl Debug { let x: u32 = foo(); loop {} } ``` errs (old solver): ```rust fn eq<T>(a: T, _: T) -> T { a } fn foo() -> impl Sized { let x = eq(foo(), 1u32); println!("{x}"); // error :( loop {} } ``` (Workaround is:) ```rust fn eq<T>(a: T, _: T) -> T { a } fn foo() -> impl Sized { let x = eq(foo(), 1u32); println!("{}", x as u32); // OK loop {} } ``` - Happens in super_combine for all relations (eq and sub don’t have the opaque handling code special to them) - Opaques can end up being revealed locally later on - Pre-populate borrowck - no fulfillment context Problems with new solver: - Opaques can only be defined once modulo regions - opaque use in the defining scope ```rust type TAIT = impl Display; fn use(x: TAIT) { // this side-steps autoderef Display::fmt(&x, ...); // in old solver: this works // in new solver: errors with ambiguity x.fmt(...) } fn define() -> TAIT { 1 } ``` ```rust // Compiles today // Fails in new solver fn rpit_example(n: u32) -> impl std::fmt::Display { if n > 0 { rpit_example(n - 1).to_string() } else { format!("Hello, Ferris") } } fn main() { } ``` project goals in non-defining use in defining scope: https://rust.godbolt.org/z/e5sbrrjP8 ```rust trait TAIT = impl Iterator<Item = u32>; fn impls_iter<T: Iterator<Item = U>, U>(_: T) {} fn non_defining_use(x: TAIT) { impls_iter(x) } ``` TC: Is there an old-solver/new-solver difference for?: https://rust.godbolt.org/z/1cTh8r5Mh ```rust #![feature(type_alias_impl_trait)] type Foo = impl Sized; fn foo() -> Foo {} fn bar() -> impl Sized { foo() } //^~ error: opaque type's hidden type cannot be another opaque type from the same scope // Question is: // - do we choose to constrain `Foo` to the RPIT of `bar` (error) or vice versa (ok) ``` ## How do we align new and old solver behavior * Pass in a specific defining anchor instead of bubble * this will impact caching * something something wf ## Expected outcomes - Some more confidence about the future of TAIT and its interaction with the new trait solver. - ? ## Key questions for discussion - ? ## Notes from the meetup itself Niko: unsound examples of opaque types? https://github.com/rust-lang/rust/issues?q=is%3Aopen+label%3AI-unsound+label%3AA-impl-trait NM: What do we have to do to stabilize TAIT before the new trait solver is stabilized? CE: I feel like, with a couple of exceptions, the new solver accepts more code than the old solver. ```rust // Ok only in old solver, fundamental limitation in new solver type Tait<'x> = impl Sized; fn foo<'a, 'b>(x: &'a (), y: &'b ()) -> (Tait<'a>, Tait<'b>) { (x, y) } // Ok in new and old solver type Tait<T> = impl Sized; fn foo<X, Y>(x: X, y: Y) -> (Tait<X>, Tait<Y>) { (x, y) } // hacky workaround fn foo<'a, 'b, T, U>(x: T, y: U) -> (Tait<T>, Tait<U>) where T: Into<&'a ()>, U: Into<&'b ()>, { (x, y) } ``` TC: What if it's non-defining? ```rust #![feature(type_alias_impl_trait)] // OK in old solver, overflow in new solver. type Tait<'x> = impl Sized; fn define<'x>(x: &'x u8) -> Tait<'x> { x } fn non_defining<'a, 'b>(x: &'a u8, y: &'b u8) -> (Tait<'a>, Tait<'b>) { (define(x), define(y)) // ^~ error[E0275]: overflow evaluating the requirement `Tait<'_> <: Tait<'a>` } ``` https://rust.godbolt.org/z/TnsjeP4xP Projection goals non-defining use in defining scope ```rust= // passes with old impl, ambiguous with the new one. #![feature(type_alias_impl_trait)] trait Trait { type Assoc; } impl<T: Copy> Trait for T { type Assoc = T; } fn needs_trait<T: Trait<Assoc = T>>() {} type Tait = impl Copy; fn define() -> Tait {} fn check() { needs_trait::<Tait>(); //[new]~^ ERROR type annotations needed //[new]~| NOTE cannot satisfy `<Tait as Trait>::Assoc == Tait` } fn main() {} ``` * oli made a bad PR :laughing: * https://github.com/rust-lang/rust/pull/113169 ## Things to do * remove `Bubble` and always pass in the actual anchor, test perf on old solver * `opaque_types_defined_by` query should return not just a list of opaques in the signature, but also a list of opaques that are in scope (and thus are in their defining scope) * need to walk up the module tree and check all direct module members for type aliases that define TAITs (not walk into children of anything). * get rid of `check_opaque_type_well_formed` or `check_opaque_meets_bounds` * error if the same opaque type is constrained twice (with different lifetimes) in the same body (to be forward compat with the new solver) * error on RPITs that are only constrained in dead code (to fix https://github.com/rust-lang/rust/issues/112417) * make `opaque_types_defined_by` reject functions that mention the same TAIT twice with different lifetime params * Error if projection in signature normalizes to include a TAIT. * opaque_types_defined_by may have cycle errors if we do that (says errs, oli thinks he already implemented that and go no cycles) * go through uses of `DefineOpaqueTypes::No` and either document or change them * `E-easy`: flip it to `Yes`, see what tests fail, document the site by mentioning the test. If no tests fail, change them and leave a comment that there's no test excersising it. * -Zunleash-the-tait-inside-of-you to turn off any errors that are not needed for soundness (e.g. only needed for semver) ----- # Min-TAIT discussion - Date: 2023-10-11 ## Technical constraints... - Cannot define TAITs twice modulo regions (New) :fearful: but :heavy_check_mark: - This constraint will remain - Can make the old solver act more like this - can we create good diagnostics here - also affects RPIT - need additional book-keeping in new solver to be able to improve the region error: open an issue for this, not blocking anything - Should make it an error when we have a defining function with two taits modulo regions - TAITs act weirdly when aliases are in its defining usage's generic arguments (New) :heavy_check_mark: - We can fix this in the new trait solver - improve diagnostics plz - underspecified where we define TAITs and where we treat them as rigid (New, Old) - TAITs being defined to other TAIT :heavy_check_mark: - One defining scope, one not-defining scope (Old) - should define, possible by always providing the defining scope - achievable if we don't use bubble 🫧🫧🫧 - Both defining scope (New, old) - should always either hard error or be ambig - auto trait leakage (New, old) :heavy_check_mark: - Cycle errors (revealing auto traits) - non-defining use in defining scope in new solver? doesn't work once there is an auto trait bound - `Projection` goals for non-defining use in defining scope (New) :heavy_check_mark: - defining scope changes will be breaking (new, old) - (e.g.) No projection normalization in signature restriction - could make everything defining and error if it's not allowed - forces us to handle non-defining uses in defining scopes ```rust // Emit an eager error here? type Tait<'x> = impl Sized; fn define<'a, 'b>(x: &'a u8, y: &'b u8) -> (Tait<'a>, Tait<'b>) { (x, y) } ``` ```rust // OK in old solver, overflow in new solver. type Tait<'x> = impl Sized; fn define<'x>(x: &'x u8) -> Tait<'x> { x } fn non_defining<'a, 'b>(x: &'a u8, y: &'b u8) -> (Tait<'a>, Tait<'b>) { (define(x), define(y)) // ^~ error[E0275]: overflow evaluating the requirement `Tait<'_> <: Tait<'a>` } ``` ```rust // OK in old solver and new solver. mod m { pub type Tait<'x> = impl Sized; pub fn define<'x>(x: &'x u8) -> Tait<'x> { x } } use m::*; fn non_defining<'a, 'b>(x: &'a u8, y: &'b u8) -> (Tait<'a>, Tait<'b>) { (define(x), define(y)) } // https://rust.godbolt.org/z/7M89PhWxW struct Inv<'a>(Cell<&'a ()>); type Tait2<'x> = impl Sized; fn non_defining_2<'a>(x: Inv<'a>) -> Tait2<'a> { let q = (); let p = &q; if false { let _: i32 = non_defining_2(Inv(Cell::new(p))); } 1i32 } ``` ## ...and how to avoid them List of core issues: - non-defining use in defining scope is not allowed - we check it during type_of or whatever ideas: - Only define TAIT once - Maybe once modulo regions, maybe once per body, once at all - Treat only taits in signature as defining scope Conceptually, we want TAIT to act as much like RPIT as possible. - oli: having TAIT in arguments is not much more complicated - errs: let statements too? - lcnr: If the tait is somewhere in signature, theoretically it doesn't matter if you're able to name the TAIT in the function. It does matter if you can name the TAIT, people are gonna do weird stuff with it. - errs: If you define it once, then you can name it any time you want. - lcnr: It would prevent non-defining usages in defining scope, though. ```rust // The minimum desirable? mod tait { type Tait = impl Sized; struct Foo(Tait); fn must_define() -> Foo { Foo(()) } } ``` oli: Annoying rustc changes: - https://github.com/rust-lang/rust/pull/113169/files#diff-39dc2906167489754ec214d1c9375ae076b3e70193c40daa00ee702ff658b006 - https://github.com/rust-lang/rust/pull/113169/files#diff-53c8c413368c8fc6167648c14aefe191cba4ac675fdf87935e693b9442519f9f Newtype replacement pattern: ```rust #![feature(type_alias_impl_trait)] fn is_send<T: Send>(_t: &T) {} pub use self::hidden::Opaque; struct Concrete; mod hidden { use super::Concrete; pub type Opaque = impl Sized; pub(super) fn into_opaque(x: Concrete) -> Opaque { x } pub(super) fn from_opaque(x: Opaque) -> Concrete { x as Concrete } } pub fn init() -> Opaque { hidden::into_opaque(Concrete) } pub fn frob(x: Opaque) -> Opaque { is_send(&x); // No cycle errors. let x: Concrete = hidden::from_opaque(x); hidden::into_opaque(x) } ``` ### Proposal :zero: (preferred) - Forever: - A function must have the TAIT (or a type that contains it) in the signature. - or some defines syntax in the future - For now (we never expect to relax): - TAITs can only be defined once modulo regions. - For now (can be relaxed later): - A function that mentions the TAIT (or a type that contains it) in the signature, it must define it. - Because of lazy norm and how the new trait solver is more complete, this is an area of difference between the old and new solver. It would be easy to break things here. And the old trait solver is underspecified here. (Oli: It is fixable in the old solver.) So we're saving space here. - Only one function in the defining scope can mention the TAIT (or a type that contains it) in the signature. - Can create a dedicated diagnostic for this case, avoiding all cycle errors and other hard to diagnose issues for users. - This is the most arbitrary. We have the machinery to allow this. But it prevents people from writing functions that are passthrough. It allows us to write earlier and better diagnostics. But this is an artificial restriction we could lift easily. We could put this behind a separate feature gate. - Error if projection in signature normalizes to include a TAIT. - Saves space for making opaque_types_defined_by query smarter. - Properties: - All cycle errors are *real* cycle errors in the new solver. - Changes that allow more items to define the TAIT to the signature rule would be breaking changes. #### future work - non-defining uses in the defining scope, "pass through pattern" - defining uses outside of the defining scope: defines syntax - attribute can be used to mark multiple (all defining!) functions as defining, and then the error goes away - multiple functions can define the same TAIT - change signature check to normalize projections - normalizing to opaque types has to be a hard error before then, otherwise people use this as a design pattern oli: if defines attr acts like an XOR then it's simple. lcnr: XOR behavior is bad for IDEs ```rust type Alias = impl Iterator<Item = u32>; fn wrapper() -> Alias { define(0) } fn define(x: u32) -> Alias { [x].into_iter() } ``` Defines in the WC outside of the defining scope: ```rust mod taits { pub type Foo<T> = impl Future<Output = T>; } use taits::*; fn make_foo<T>() -> Foo<T> where defines(Foo<T>) { todo!() } ``` https://docs.rs/tokio-util/latest/tokio_util/sync/struct.ReusableBoxFuture.html ### Proposal :one: - modules with a tait can only have a single function - strictly more restrictive than proposal 0 ### Proposal :two: :skull_and_crossbones: - attributes ftw has to match fn to tait? - annoying to implement :< - unclear how exactly it should work, requires a bunch of design work - probably gonna be blocked by T-lang ### Proposal :three: oli: Only allow one usage of a TAIT in the entire crate errs: no ### design constraints - future compatibility - clearly communicate constraints, e.g. lints/clear errors

Import from clipboard

Paste your markdown or webpage here...

Advanced permission required

Your current role can only read. Ask the system administrator to acquire write and comment permission.

This team is disabled

Sorry, this team is disabled. You can't edit this note.

This note is locked

Sorry, only owner can edit this note.

Reach the limit

Sorry, you've reached the max length this note can be.
Please reduce the content or divide it to more notes, thank you!

Import from Gist

Import from Snippet

or

Export to Snippet

Are you sure?

Do you really want to delete this note?
All users will lose their connection.

Create a note from template

Create a note from template

Oops...
This template has been removed or transferred.
Upgrade
All
  • All
  • Team
No template.

Create a template

Upgrade

Delete template

Do you really want to delete this template?
Turn this template into a regular note and keep its content, versions, and comments.

This page need refresh

You have an incompatible client version.
Refresh to update.
New version available!
See releases notes here
Refresh to enjoy new features.
Your user state has changed.
Refresh to load new user state.

Sign in

Forgot password

or

By clicking below, you agree to our terms of service.

Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
Wallet ( )
Connect another wallet

New to HackMD? Sign up

Help

  • English
  • 中文
  • Français
  • Deutsch
  • 日本語
  • Español
  • Català
  • Ελληνικά
  • Português
  • italiano
  • Türkçe
  • Русский
  • Nederlands
  • hrvatski jezik
  • język polski
  • Українська
  • हिन्दी
  • svenska
  • Esperanto
  • dansk

Documents

Help & Tutorial

How to use Book mode

Slide Example

API Docs

Edit in VSCode

Install browser extension

Contacts

Feedback

Discord

Send us email

Resources

Releases

Pricing

Blog

Policy

Terms

Privacy

Cheatsheet

Syntax Example Reference
# Header Header 基本排版
- Unordered List
  • Unordered List
1. Ordered List
  1. Ordered List
- [ ] Todo List
  • Todo List
> Blockquote
Blockquote
**Bold font** Bold font
*Italics font* Italics font
~~Strikethrough~~ Strikethrough
19^th^ 19th
H~2~O H2O
++Inserted text++ Inserted text
==Marked text== Marked text
[link text](https:// "title") Link
![image alt](https:// "title") Image
`Code` Code 在筆記中貼入程式碼
```javascript
var i = 0;
```
var i = 0;
:smile: :smile: Emoji list
{%youtube youtube_id %} Externals
$L^aT_eX$ LaTeX
:::info
This is a alert area.
:::

This is a alert area.

Versions and GitHub Sync
Get Full History Access

  • Edit version name
  • Delete

revision author avatar     named on  

More Less

Note content is identical to the latest version.
Compare
    Choose a version
    No search result
    Version not found
Sign in to link this note to GitHub
Learn more
This note is not linked with GitHub
 

Feedback

Submission failed, please try again

Thanks for your support.

On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

Please give us some advice and help us improve HackMD.

 

Thanks for your feedback

Remove version name

Do you want to remove this version name and description?

Transfer ownership

Transfer to
    Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

      Link with GitHub

      Please authorize HackMD on GitHub
      • Please sign in to GitHub and install the HackMD app on your GitHub repo.
      • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
      Learn more  Sign in to GitHub

      Push the note to GitHub Push to GitHub Pull a file from GitHub

        Authorize again
       

      Choose which file to push to

      Select repo
      Refresh Authorize more repos
      Select branch
      Select file
      Select branch
      Choose version(s) to push
      • Save a new version and push
      • Choose from existing versions
      Include title and tags
      Available push count

      Pull from GitHub

       
      File from GitHub
      File from HackMD

      GitHub Link Settings

      File linked

      Linked by
      File path
      Last synced branch
      Available push count

      Danger Zone

      Unlink
      You will no longer receive notification when GitHub file changes after unlink.

      Syncing

      Push failed

      Push successfully