Try   HackMD

This Month in @compiler-errors (rustc contributions) - October

I've seen some "this month in XYZ" posts recently, and I'm interested in posting my own, highlighting a variety of things I've been contributing to in the Rust compiler in the last month. This covers the month of October, so it's a bit out of date.

More details

Although I think I'm quite a prolific contributor to the Rust compiler, I often forget exactly what I'm doing day to day and week to week.

My contributions often come in response to my being made aware of things that need urgent help, pings from people, hearing about things from the tweet-o-sphere, or most rarely, just having a strike of random inspiration.

So, ironically, when I'm "between" work, I often feel like motivation is difficult to find, and feel a bit of despair that maybe I've finally run out of things to do impostor syndrome sucks! And it never goes away! Hopefully these blog post will help with that.

Const traits

Of all the things I've done in the last month, the one that I am most proud of is reworking the internal representation of const traits in the compiler. Const traits were originally proposed in RFC 2632, then revamped heavily into a design that is still yet to be RFC'd (drafted here), but still is being heavily experimented on[1].

In #131985 "Represent trait constness as a distinct predicate", I completely reworked the way that we represent and validate const and ~const trait bounds. This work came out of a discussion I had on a messaging platform:

errs: i wonder if we should actually just represent this effectness as a separate predicate
errs: rather than an associated const
[]
errs: bc what we have is an only-if relationship
errs: we could stop worrying about this higher-ranked stuff if we had a specific (perhaps set of) predicate that represented the effects rather than trying to use traits and projections

For those who are more interested in the technical details, I highly recommend reading the description of the PR linked above. It also includes some really detailed background about the previous few approaches to implementing const traits and const trait bounds.

Due to this rework, I was able to implement support for ~const in "item bound" position (#132118 "Add support for ~const item bounds"), i.e., in an associated type bound like:

#[const_trait]
trait Foo {
    type Type: ~const Bar;
}

Also, we had previously introduced the artificial limitation that const traits only worked correctly when the new trait solver (-Znext-solver) was enabled. In #132119 "Hack out effects support for old solver", we were able to relax that. Since the standard library builds using the old, stable trait solver today, this unlocks the reintroduction of const traits in the near future, which were removed when the previous feature(effects) rewrite of const traits began.

This work also led to some nice compiler cleanups (#132344, #131652, #131653, #131968, #132368).

Side-note: bound modifiers

I'm not certain if it was totally due to this ~const rework that I did, but I did spend some time also reworking how we represented and lowered "trait bound modifiers" in the frontend of the compiler. Trait bound modifiers are the bits that "modify" a trait bound think ? on Sized, and const/~const on const traits, along with the async keyword proposed to modify Fn in RFC 3668 "Async closures". This work led to some nice compiler cleanups (#131931, #131981, #131982).

However, it hilariously led to an uncovering of a bug that we were not validating. While this was fixed in #132209 "Fix validation when lowering ? trait bounds", we did silently accept this code for a while:

fn test<T: ?Sized<i32>>() {}

For the record, this was not unsound or anything. Just nonsense.

I also worked on some improvements to items relating to the Rust edition 2024 release, which happens soon.

In #132383 "Implement suggestion for never type fallback lints", I implemented an improvement to the two lints surrounding never-type fallback changes in Rust 2024. They should allow the automatic application of type annotations that should preserve old never-type fallback behavior in the new edition.

warning: this function depends on never type fallback being `()`
  --> $DIR/never-type-fallback-breaking.rs:15:1
   |
LL | fn m() {
   | ^^^^^^
   |
note: in edition 2024, the requirement `!: Default` will fail
  --> $DIR/never-type-fallback-breaking.rs:19:17
   |
LL |         true => Default::default(),
   |                 ^^^^^^^^^^^^^^^^^^
   = note: `#[warn(dependency_on_unit_never_type_fallback)]` on by default
+ help: use `()` annotations to avoid fallback changes
+    |
+ LL |     let x: () = match true {
+    |          ++++

Along a similar vein, in Rust 2024 we plan on changing the defaults around impl Trait types capturing lifetimes. This blog post explains the rationale, and while it's the more sane default and we expect users not to care much about the change, it can still result in borrow-checker errors. In #131186 "Try to point out when edition 2024 lifetime capture rules cause borrowck issues", this implements an additional note to point out what is happening:

error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable
  --> $DIR/migration-note.rs:20:5
   |
LL |     let a = display_len(&x);
   |                         -- immutable borrow occurs here
...
LL |     x.push(1);
   |     ^^^^^^^^^ mutable borrow occurs here
...
LL |     println!("{a}");
   |               --- immutable borrow later used here
   |
+ note: this call may capture more lifetimes than intended, because Rust 2024 has adjusted the `impl Trait` lifetime capture rules
+   --> $DIR/migration-note.rs:17:13
+    |
+ LL |     let a = display_len(&x);
+    |             ^^^^^^^^^^^^^^^
+ help: add a precise capturing bound to avoid overcapturing
+    |
+ LL | fn display_len<T>(x: &Vec<T>) -> impl Display + use<T> {
+    |                                               ++++++++

I also tweaked a confusing error message for extern {} blocks in #131550 "Make some tweaks to extern block diagnostics" having to do with unsafe fn in "unadorned" extern {} blocks, and fixed the pretty-printing of the safe keyword in #132088 "Print safety correctly in extern static items".

Improvements to return-type notation

Another feature I've been working on polishing is "return-type notation". I think it fills an important expressivity gap with the AFIT (async fn in trait) feature I stabilized late last year, and am quite excited to see it eventually go up for stabilization.

In #132194 "Collect item bounds for RPITITs from trait where clauses just like associated types", I fixed an incongruency between associated types and RPITIT (return-position impl trait in trait) where users can write where-clauses to add additional bounds onto their RPITITs:

trait Foo
where
    Self::method(..): Send,
{
    async fn method();
}

While it may not seem super useful, it does give users a (not yet stable) way to add bounds to their async fn returned Future without needing to desugar it into impl Future. This fix also uncovered a subtle bug which led to "shorthand projections" like T::Assoc (without a trait) to fail in AFIT trait bounds, which I fixed in #132373 "Make sure type_param_predicates resolves correctly for RPITIT".

Async closures

I worked a bit on async closures, fixing how we formatted for<'a> async Fn() (#131657) bounds, and fixing a bug with coverage (#131802). By the end of October, this feature is basically ready for stabilization.

Unsoundness fixes

I fixed a couple of unsoundnesses last month. In #131201 "Disable jump threading UnOp::Not for non-bool", we realized that the jump threading implementation wasn't treating non-bool types correctly (i.e. integers) for the Not operator.

I fixed another nightly-feature-only unsoundness in #132151 "Ensure that resume arg outlives region bound for coroutines", which had to do with lifetime-outlives and the resume type of a coroutine. Since coroutines support was built out mostly to support async (and it does so soundly, afaict), it's not totally polished this is an area I've occasionally fixed in the past.

Raw lifetimes

Raw lifetimes are stabilizing soon. They look like 'r#async, and give people the ability to give lifetimes the names of keywords in the same way that raw identifiers can.

When working on the reference guide section for raw lifetimes, a few incongruencies were helpfully pointed out, which I fixed in #132341 "Reject raw lifetime followed by ' , like regular lifetimes do" and #132363 "Enforce that raw lifetimes must be valid raw identifiers".

New trait solver + librarification

I've worked a bit on the librarification of the new trait solver, in preparation for its integration into rust-analyzer. In #131263 "Introduce SolverRelating type relation to the new solver", I abstracted out the "type relation" used by the new trait solver. This is the API that can be used to tell whether two types are equal and powers type inference. This led to a nice cleanup (#131343) in the old trait solver's API, too.

I worked a bit to improve diagnostics for trait errors in the new trait solver. #131699 "Try to improve error messages involving aliases in the solver" sets up the compiler to better deduplicate error messages for associated types, and #131756 "Deeply normalize TypeTrace when reporting type error in new solver" helps simplify the types we report in type error messages.

Finally, I worked a bit on fixing the type checker to be compatible with the new trait solver.

The main issue is that the type checker is not compatible with the "lazy normalization" implemented in the new trait solver. In other words, when we have an associated type that we know how to simplify, like <[i32; 1] as IntoIterator>::Item (which is i32), we don't always check that it is simplified before structurally matching on that type (e.g. checking that the type is a reference), since the old trait solver eagerly simplified associated types whenever it could. A few missing places were fixed in #131482 and #131751.

Diagnostics improvements

The theme for diagnostics improvements this month were "fix diagnostic bugs that were annoying myself and others".

In #131754 "Don't report bivariance error when nesting a struct with field errors into another struct", I was made aware on twitter of spurious errors for malformed structs with derive()s on them. That PR suppresses them.

#131702 "Suppress import errors for traits that couldve applied for method lookup error" suppresses import warnings for cases where there is another method lookup error. For example, when I have:

use some_crate::Trait;

something.call_method();

where call_method() should resolve to Trait::call_method() but doesn't apply (e.g. because something does not implement it), we should not be reporting an unused import warning, since the primary error to fix here is the method lookup failure, which will likely fix the import warning after that trait now becomes used.

error[E0599]: no method named `call_method` found for struct `Something` in the current scope
  --> main.rs:15:9
   |
15 |     something.call_method();
   |               ^^^^^^^^^^^ method not found in `Something`
   |
   = help: items from traits can only be used if the trait is implemented and in scope
note: `Trait` defines an item `call_method`, perhaps you need to implement it
  --> other_file.rs:7:5
   |
7  |     pub trait Something {
   |     ^^^^^^^^^^^^^^^^^^^

- warning: unused import: `foo::Bar`
-  --> src/main.rs:4:5
-   |
- 4 | use some_crate::Trait;
-   |     ^^^^^^^^^^^^^^^^^
-   |
-   = note: `#[warn(unused_imports)]` on by default

#131549 "Add a note for ? on a impl Future<Output = Result<..>> in sync function" fixes an incongruency where if you are in a non-async function, and you have a future that yields a Result and you try to apply the ? operator to it, we mention nothing. We already had the machinery to mention .awaiting it in an async function, but this just implements a note just acknowledging it.

In #131795 "Stop inverting expectation in normalization errors", I fixed a bug where we were reporting the wrong "expected" and "found" types in an associated type mismatch error.

That PR is a common case where in diagnostics where I find that simpler is better. When we begin to special-case things, it often happens that the special casing begins to "bitrot", and users hit corners that the original author never expected. That PR does end up regressing try {} block return type error reporting, but I think that needs to be handled in a more sophisticated way, especially since the type inference of try {} is likely to need changing in the future.

Finally, #131840 "Dont consider predicates that may hold as impossible in is_impossible_associated_item" fixes a rustdoc bug where we were documenting trait methods with where clauses with generics, like

impl Foo {
    fn needs_sized(&self)
    where
        Self: Sized,
    {}
}

pub struct Generic<T: ?Sized>(T);

impl<T: ?Sized> Foo for Generic<T> {}

where we were not documenting the method needs_sized in the impl Foo for Generic<T>, since needs_sized doesn't hold for all T: ?Sized. However, this method still exists, since I could very easily call it for e.g. T = String, or any other sized type.

This bug came about because of a change I made a few years ago to not document methods with trivially-unsatisfiable where clauses (#100221). However, this didn't consider generic parameters correctly.

Some compiler cleanups and ICE fixes

I worked on a couple handfuls of other cleanups and ICE fixes, but I honestly got too tired to write this section, and it was probably too technical to interest many anyways

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →


  1. I think this work has highlighted both the effectiveness and importance of pre-RFC experimentation of features in the compiler. While I applaud the original RFC for proposing what I think is one of the most important not-yet-stable features in rustc like for crying out loud what do you mean we can't call methods in const functions we would never have known what we knew today about the feature had it been RFC'd in its original state. ↩︎