Document not found (404)
+This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ +diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000..f17311098 --- /dev/null +++ b/.nojekyll @@ -0,0 +1 @@ +This file makes sure that Github Pages doesn't process mdBook's output. diff --git a/404.html b/404.html new file mode 100644 index 000000000..d8a0389af --- /dev/null +++ b/404.html @@ -0,0 +1,191 @@ + + +
+ + +This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ +This guide is meant to help document how rustc – the Rust compiler – works, +as well as to help new contributors get involved in rustc development.
+There are seven parts to this guide:
+rustc
:
+Contains information that should be useful no matter how you are contributing,
+about building, debugging, profiling, etc.rustc
:
+Contains information that should be useful no matter how you are contributing,
+about procedures for contribution, using git and Github, stabilizing features, etc.Keep in mind that rustc
is a real production-quality product,
+being worked upon continuously by a sizeable set of contributors.
+As such, it has its fair share of codebase churn and technical debt.
+In addition, many of the ideas discussed throughout this guide are idealized designs
+that are not fully realized yet.
+All this makes keeping this guide completely up to date on everything very hard!
The Guide itself is of course open-source as well, +and the sources can be found at the GitHub repository. +If you find any mistakes in the guide, please file an issue about it. +Even better, open a PR with a correction!
+If you do contribute to the guide, +please see the corresponding subsection on writing documentation in this guide.
+++“‘All conditioned things are impermanent’ — +when one sees this with wisdom, one turns away from suffering.” +The Dhammapada, verse 277
+
You might also find the following sites useful:
+#contribute
and #wg-rustup
on Discord.* -> vec
should find all
+functions that return a Vec<T>
.
+Hint: Find more tips and keyboard shortcuts by typing ?
on any Rustdoc
+page!This section covers a numbers of common compiler terms that arise in +this guide. We try to give the general definition while providing some +Rust-specific context.
+ +A control-flow graph (CFG) is a common term from compilers. If you've ever +used a flow-chart, then the concept of a control-flow graph will be +pretty familiar to you. It's a representation of your program that +clearly exposes the underlying control flow.
+A control-flow graph is structured as a set of basic blocks +connected by edges. The key idea of a basic block is that it is a set +of statements that execute "together" – that is, whenever you branch +to a basic block, you start at the first statement and then execute +all the remainder. Only at the end of the block is there the +possibility of branching to more than one place (in MIR, we call that +final statement the terminator):
+bb0: {
+ statement0;
+ statement1;
+ statement2;
+ ...
+ terminator;
+}
+
+Many expressions that you are used to in Rust compile down to multiple +basic blocks. For example, consider an if statement:
+a = 1;
+if some_variable {
+ b = 1;
+} else {
+ c = 1;
+}
+d = 1;
+
+This would compile into four basic blocks in MIR. In textual form, it looks like +this:
+BB0: {
+ a = 1;
+ if some_variable {
+ goto BB1;
+ } else {
+ goto BB2;
+ }
+}
+
+BB1: {
+ b = 1;
+ goto BB3;
+}
+
+BB2: {
+ c = 1;
+ goto BB3;
+}
+
+BB3: {
+ d = 1;
+ ...
+}
+
+In graphical form, it looks like this:
+ BB0
+ +--------------------+
+ | a = 1; |
+ +--------------------+
+ / \
+ if some_variable else
+ / \
+ BB1 / \ BB2
+ +-----------+ +-----------+
+ | b = 1; | | c = 1; |
+ +-----------+ +-----------+
+ \ /
+ \ /
+ \ BB3 /
+ +----------+
+ | d = 1; |
+ | ... |
+ +----------+
+
+When using a control-flow graph, a loop simply appears as a cycle in
+the graph, and the break
keyword translates into a path out of that
+cycle.
Static Program Analysis by Anders Møller +and Michael I. Schwartzbach is an incredible resource!
+Dataflow analysis is a type of static analysis that is common in many +compilers. It describes a general technique, rather than a particular analysis.
+The basic idea is that we can walk over a control-flow graph (CFG) and
+keep track of what some value could be. At the end of the walk, we might have
+shown that some claim is true or not necessarily true (e.g. "this variable must
+be initialized"). rustc
tends to do dataflow analyses over the MIR, since MIR
+is already a CFG.
For example, suppose we want to check that x
is initialized before it is used
+in this snippet:
fn foo() {
+ let mut x;
+
+ if some_cond {
+ x = 1;
+ }
+
+ dbg!(x);
+}
+
+A CFG for this code might look like this:
+ +------+
+ | Init | (A)
+ +------+
+ | \
+ | if some_cond
+ else \ +-------+
+ | \| x = 1 | (B)
+ | +-------+
+ | /
+ +---------+
+ | dbg!(x) | (C)
+ +---------+
+
+We can do the dataflow analysis as follows: we will start off with a flag init
+which indicates if we know x
is initialized. As we walk the CFG, we will
+update the flag. At the end, we can check its value.
So first, in block (A), the variable x
is declared but not initialized, so
+init = false
. In block (B), we initialize the value, so we know that x
is
+initialized. So at the end of (B), init = true
.
Block (C) is where things get interesting. Notice that there are two incoming
+edges, one from (A) and one from (B), corresponding to whether some_cond
is true or not.
+But we cannot know that! It could be the case the some_cond
is always true,
+so that x
is actually always initialized. It could also be the case that
+some_cond
depends on something random (e.g. the time), so x
may not be
+initialized. In general, we cannot know statically (due to Rice's
+Theorem). So what should the value of init
be in block (C)?
Generally, in dataflow analyses, if a block has multiple parents (like (C) in +our example), its dataflow value will be some function of all its parents (and +of course, what happens in (C)). Which function we use depends on the analysis +we are doing.
+In this case, we want to be able to prove definitively that x
must be
+initialized before use. This forces us to be conservative and assume that
+some_cond
might be false sometimes. So our "merging function" is "and". That
+is, init = true
in (C) if init = true
in (A) and in (B) (or if x
is
+initialized in (C)). But this is not the case; in particular, init = false
in
+(A), and x
is not initialized in (C). Thus, init = false
in (C); we can
+report an error that "x
may not be initialized before use".
There is definitely a lot more that can be said about dataflow analyses. There is an
+extensive body of research literature on the topic, including a lot of theory.
+We only discussed a forwards analysis, but backwards dataflow analysis is also
+useful. For example, rather than starting from block (A) and moving forwards,
+we might have started with the usage of x
and moved backwards to try to find
+its initialization.
In math, a predicate may be universally quantified or existentially +quantified:
+In Rust, they come up in type checking and trait solving. For example,
+fn foo<T>()
+
+This function claims that the function is well-typed for all types T
: ∀ T: well_typed(foo)
.
Another example:
+fn foo<'a>(_: &'a usize)
+
+This function claims that for any lifetime 'a
(determined by the
+caller), it is well-typed: ∀ 'a: well_typed(foo)
.
Another example:
+fn foo<F>()
+where for<'a> F: Fn(&'a u8)
+
+This function claims that it is well-typed for all types F
such that for all
+lifetimes 'a
, F: Fn(&'a u8)
: ∀ F: ∀ 'a: (F: Fn(&'a u8)) => well_typed(foo)
.
One more example:
+fn foo(_: dyn Debug)
+
+This function claims that there exists some type T
that implements Debug
+such that the function is well-typed: ∃ T: (T: Debug) and well_typed(foo)
.
De Bruijn indices are a way of representing, using only integers,
+which variables are bound in which binders. They were originally invented for
+use in lambda calculus evaluation (see this Wikipedia article for
+more). In rustc
, we use de Bruijn indices to represent generic types.
Here is a basic example of how de Bruijn indices might be used for closures (we
+don't actually do this in rustc
though!):
|x| {
+ f(x) // de Bruijn index of `x` is 1 because `x` is bound 1 level up
+
+ |y| {
+ g(x, y) // index of `x` is 2 because it is bound 2 levels up
+ // index of `y` is 1 because it is bound 1 level up
+ }
+}
+
+Check out the subtyping chapter from the +Rust Nomicon.
+See the variance chapter of this guide for more info on how +the type checker handles variance.
+ +Let's describe the concepts of free vs bound in terms of program +variables, since that's the thing we're most familiar with.
+|a, b| a + b
.
+Here, the a
and b
in a + b
refer to the arguments that the closure will
+be given when it is called. We say that the a
and b
there are bound to
+the closure, and that the closure signature |a, b|
is a binder for the
+names a
and b
(because any references to a
or b
within refer to the
+variables that it introduces).a + b
. In this expression, a
and b
refer to
+local variables that are defined outside of the expression. We say that
+those variables appear free in the expression (i.e., they are free,
+not bound (tied up)).So there you have it: a variable "appears free" in some +expression/statement/whatever if it refers to something defined +outside of that expressions/statement/whatever. Equivalently, we can +then refer to the "free variables" of an expression – which is just +the set of variables that "appear free".
+So what does this have to do with regions? Well, we can apply the
+analogous concept to type and regions. For example, in the type &'a u32
, 'a
appears free. But in the type for<'a> fn(&'a u32)
, it
+does not.
++Thanks to
+mem
,scottmcm
, andLevi
on the official Discord for the +recommendations, and totinaun
for posting a link to a twitter thread from +Graydon Hoare +which had some more recommendations!Other sources: https://gcc.gnu.org/wiki/ListOfCompilerBooks
+If you have other suggestions, please feel free to open an issue or PR.
+
This is a reading list of material relevant to Rust. It includes prior +research that has - at one time or another - influenced the design of +Rust, as well as publications about Rust.
+rustc has a lot of important data structures. This is an attempt to give some +guidance on where to learn more about some of the key data structures of the +compiler.
+Item | Kind | Short description | Chapter | Declaration |
---|---|---|---|---|
BodyId | struct | One of four types of HIR node identifiers | Identifiers in the HIR | compiler/rustc_hir/src/hir.rs |
Compiler | struct | Represents a compiler session and can be used to drive a compilation. | The Rustc Driver and Interface | compiler/rustc_interface/src/interface.rs |
ast::Crate | struct | A syntax-level representation of a parsed crate | The parser | compiler/rustc_ast/src/ast.rs |
rustc_hir::Crate | struct | A more abstract, compiler-friendly form of a crate's AST | The Hir | compiler/rustc_hir/src/hir.rs |
DefId | struct | One of four types of HIR node identifiers | Identifiers in the HIR | compiler/rustc_hir/src/def_id.rs |
Diag | struct | A struct for a compiler diagnostic, such as an error or lint | Emitting Diagnostics | compiler/rustc_errors/src/diagnostic.rs |
DocContext | struct | A state container used by rustdoc when crawling through a crate to gather its documentation | Rustdoc | src/librustdoc/core.rs |
HirId | struct | One of four types of HIR node identifiers | Identifiers in the HIR | compiler/rustc_hir/src/hir_id.rs |
NodeId | struct | One of four types of HIR node identifiers. Being phased out | Identifiers in the HIR | compiler/rustc_ast/src/ast.rs |
P | struct | An owned immutable smart pointer. By contrast, &T is not owned, and Box<T> is not immutable. | None | compiler/rustc_ast/src/ptr.rs |
ParamEnv | struct | Information about generic parameters or Self , useful for working with associated or generic items | Parameter Environment | compiler/rustc_middle/src/ty/mod.rs |
ParseSess | struct | This struct contains information about a parsing session | The parser | compiler/rustc_session/src/parse/parse.rs |
Query | struct | Represents the result of query to the Compiler interface and allows stealing, borrowing, and returning the results of compiler passes. | The Rustc Driver and Interface | compiler/rustc_interface/src/queries.rs |
Rib | struct | Represents a single scope of names | Name resolution | compiler/rustc_resolve/src/lib.rs |
Session | struct | The data associated with a compilation session | The parser, The Rustc Driver and Interface | compiler/rustc_session/src/session.rs |
SourceFile | struct | Part of the SourceMap . Maps AST nodes to their source code for a single source file. Was previously called FileMap | The parser | compiler/rustc_span/src/lib.rs |
SourceMap | struct | Maps AST nodes to their source code. It is composed of SourceFile s. Was previously called CodeMap | The parser | compiler/rustc_span/src/source_map.rs |
Span | struct | A location in the user's source code, used for error reporting primarily | Emitting Diagnostics | compiler/rustc_span/src/span_encoding.rs |
StringReader | struct | This is the lexer used during parsing. It consumes characters from the raw source code being compiled and produces a series of tokens for use by the rest of the parser | The parser | compiler/rustc_parse/src/lexer/mod.rs |
rustc_ast::token_stream::TokenStream | struct | An abstract sequence of tokens, organized into TokenTree s | The parser, Macro expansion | compiler/rustc_ast/src/tokenstream.rs |
TraitDef | struct | This struct contains a trait's definition with type information | The ty modules | compiler/rustc_middle/src/ty/trait_def.rs |
TraitRef | struct | The combination of a trait and its input types (e.g. P0: Trait<P1...Pn> ) | Trait Solving: Goals and Clauses | compiler/rustc_middle/src/ty/sty.rs |
Ty<'tcx> | struct | This is the internal representation of a type used for type checking | Type checking | compiler/rustc_middle/src/ty/mod.rs |
TyCtxt<'tcx> | struct | The "typing context". This is the central data structure in the compiler. It is the context that you use to perform all manner of queries | The ty modules | compiler/rustc_middle/src/ty/context.rs |
These are videos where various experts explain different parts of the compiler:
+Term | Meaning |
---|---|
arena, arena allocation | An arena is a large memory buffer from which other memory allocations are made. This style of allocation is called arena allocation. See this chapter for more info. |
AST | The abstract syntax tree produced by the rustc_ast crate; reflects user syntax very closely. |
APIT | An argument-position impl Trait . Also known as an anonymous type parameter. (see the reference). |
binder | A binder is a place where a variable or type is declared; for example, the <T> is a binder for the generic type parameter T in fn foo<T>(..) , and |a | ... is a binder for the parameter a . See the background chapter for more. |
BodyId | An identifier that refers to a specific body (definition of a function or constant) in the crate. See the HIR chapter for more. |
bound variable | A bound variable is one that is declared within an expression/term. For example, the variable a is bound within the closure expression |a | a * 2 . See the background chapter for more |
codegen | Short for code generation. The code to translate MIR into LLVM IR. |
codegen unit | When we produce LLVM IR, we group the Rust code into a number of codegen units (sometimes abbreviated as CGUs). Each of these units is processed by LLVM independently from one another, enabling parallelism. They are also the unit of incremental re-use. (see more) |
completeness | A technical term in type theory, it means that every type-safe program also type-checks. Having both soundness and completeness is very hard, and usually soundness is more important. (see "soundness"). |
control-flow graph | A representation of the control-flow of a program; see the background chapter for more |
CTFE | Short for compile-time function evaluation, this is the ability of the compiler to evaluate const fn s at compile time. This is part of the compiler's constant evaluation system. (see more) |
cx | We tend to use cx as an abbreviation for context. See also tcx , infcx , etc. |
ctxt | We also use ctxt as an abbreviation for context, e.g. TyCtxt . See also cx or tcx. |
DAG | A directed acyclic graph is used during compilation to keep track of dependencies between queries. (see more) |
data-flow analysis | A static analysis that figures out what properties are true at each point in the control-flow of a program; see the background chapter for more. |
de Bruijn index | A technique for describing which binder a variable is bound by using only integers. It has the benefit that it is invariant under variable renaming. (see more) |
DefId | An index identifying a definition (see rustc_middle/src/hir/def_id.rs ). Uniquely identifies a DefPath . See the HIR chapter for more. |
discriminant | The underlying value associated with an enum variant or generator state to indicate it as "active" (but not to be confused with its "variant index"). At runtime, the discriminant of the active variant is encoded in the tag. |
double pointer | A pointer with additional metadata. See fat pointer for more. |
drop glue | (Internal) compiler-generated instructions that handle calling the destructors (Drop ) for data types. |
DST | Short for dynamically-sized type, this is a type for which the compiler cannot statically know the size in memory (e.g. str or [u8] ). Such types don't implement Sized and cannot be allocated on the stack. They can only occur as the last field in a struct. They can only be used behind a pointer (e.g. &str or &[u8] ). |
early-bound lifetime | A lifetime region that is substituted at its definition site. Bound in an item's Generics and substituted/instantiated using a GenericArgs . Contrast with late-bound lifetime. (see more) |
effects | Right now only means const traits and ~const bounds. (see more) |
empty type | See uninhabited type. |
fat pointer | A two word value carrying the address of some value, along with some further information necessary to put the value to use. Rust includes two kinds of fat pointers: references to slices, and trait objects. A reference to a slice carries the starting address of the slice and its length. A trait object carries a value's address and a pointer to the trait's implementation appropriate to that value. "Fat pointers" are also known as "wide pointers", and "double pointers". |
free variable | A free variable is one that is not bound within an expression or term; see the background chapter for more |
generics | The list of generic parameters defined on an item. There are three kinds of generic parameters: Type, lifetime and const parameters. |
HIR | The high-level IR, created by lowering and desugaring the AST. (see more) |
HirId | Identifies a particular node in the HIR by combining a def-id with an "intra-definition offset". See the HIR chapter for more. |
HIR map | The HIR map, accessible via tcx.hir() , allows you to quickly navigate the HIR and convert between various forms of identifiers. |
ICE | Short for internal compiler error, this is when the compiler crashes. |
ICH | Short for incremental compilation hash, these are used as fingerprints for things such as HIR and crate metadata, to check if changes have been made. This is useful in incremental compilation to see if part of a crate has changed and should be recompiled. |
infcx | The type inference context (InferCtxt ). (see rustc_middle::infer ) |
inference variable, infer var | When doing type, region, const inference, an inference variable is a kind of special type/region that represents what you are trying to infer. Think of X in algebra. For example, if we are trying to infer the type of a variable in a program, we create an inference variable to represent that unknown type. |
intern | Interning refers to storing certain frequently-used constant data, such as strings, and then referring to the data by an identifier (e.g. a Symbol ) rather than the data itself, to reduce memory usage and number of allocations. See this chapter for more info. |
interpreter | The heart of const evaluation, running MIR code at compile time. (see more) |
intrinsic | Intrinsics are special functions that are implemented in the compiler itself but exposed (often unstably) to users. They do magical and dangerous things. (See std::intrinsics ) |
IR | Short for intermediate representation, a general term in compilers. During compilation, the code is transformed from raw source (ASCII text) to various IRs. In Rust, these are primarily HIR, MIR, and LLVM IR. Each IR is well-suited for some set of computations. For example, MIR is well-suited for the borrow checker, and LLVM IR is well-suited for codegen because LLVM accepts it. |
IRLO, irlo | Sometimes used as an abbreviation for internals.rust-lang.org. |
item | A kind of "definition" in the language, such as a static, const, use statement, module, struct, etc. Concretely, this corresponds to the Item type. |
lang item | Items that represent concepts intrinsic to the language itself, such as special built-in traits like Sync and Send ; or traits representing operations such as Add ; or functions that are called by the compiler. (see more) |
late-bound lifetime | A lifetime region that is substituted at its call site. Bound in a HRTB and substituted by specific functions in the compiler, such as liberate_late_bound_regions . Contrast with early-bound lifetime. (see more) |
local crate | The crate currently being compiled. This is in contrast to "upstream crates" which refer to dependencies of the local crate. |
LTO | Short for link-time optimizations, this is a set of optimizations offered by LLVM that occur just before the final binary is linked. These include optimizations like removing functions that are never used in the final program, for example. ThinLTO is a variant of LTO that aims to be a bit more scalable and efficient, but possibly sacrifices some optimizations. You may also read issues in the Rust repo about "FatLTO", which is the loving nickname given to non-Thin LTO. LLVM documentation: here and here. |
LLVM | (actually not an acronym :P) an open-source compiler backend. It accepts LLVM IR and outputs native binaries. Various languages (e.g. Rust) can then implement a compiler front-end that outputs LLVM IR and use LLVM to compile to all the platforms LLVM supports. |
memoization | The process of storing the results of (pure) computations (such as pure function calls) to avoid having to repeat them in the future. This is typically a trade-off between execution speed and memory usage. |
MIR | The mid-level IR that is created after type-checking for use by borrowck and codegen. (see more) |
Miri | A tool to detect Undefined Behavior in (unsafe) Rust code. (see more) |
monomorphization | The process of taking generic implementations of types and functions and instantiating them with concrete types. For example, in the code we might have Vec<T> , but in the final executable, we will have a copy of the Vec code for every concrete type used in the program (e.g. a copy for Vec<usize> , a copy for Vec<MyStruct> , etc). |
normalize | A general term for converting to a more canonical form, but in the case of rustc typically refers to associated type normalization. |
newtype | A wrapper around some other type (e.g., struct Foo(T) is a "newtype" for T ). This is commonly used in Rust to give a stronger type for indices. |
niche | Invalid bit patterns for a type that can be used for layout optimizations. Some types cannot have certain bit patterns. For example, the NonZero* integers or the reference &T cannot be represented by a 0 bitstring. This means the compiler can perform layout optimizations by taking advantage of the invalid "niche value". An example application for this is the Discriminant elision on Option -like enums, which allows using a type's niche as the "tag" for an enum without requiring a separate field. |
NLL | Short for non-lexical lifetimes, this is an extension to Rust's borrowing system to make it be based on the control-flow graph. |
node-id or NodeId | An index identifying a particular node in the AST or HIR; gradually being phased out and replaced with HirId . See the HIR chapter for more. |
obligation | Something that must be proven by the trait system. (see more) |
placeholder | NOTE: skolemization is deprecated by placeholder a way of handling subtyping around "for-all" types (e.g., for<'a> fn(&'a u32) ) as well as solving higher-ranked trait bounds (e.g., for<'a> T: Trait<'a> ). See the chapter on placeholder and universes for more details. |
point | Used in the NLL analysis to refer to some particular location in the MIR; typically used to refer to a node in the control-flow graph. |
polymorphize | An optimization that avoids unnecessary monomorphisation. (see more) |
projection | A general term for a "relative path", e.g. x.f is a "field projection", and T::Item is an "associated type projection". |
promoted constants | Constants extracted from a function and lifted to static scope; see this section for more details. |
provider | The function that executes a query. (see more) |
quantified | In math or logic, existential and universal quantification are used to ask questions like "is there any type T for which is true?" or "is this true for all types T?"; see the background chapter for more. |
query | A sub-computation during compilation. Query results can be cached in the current session or to disk for incremental compilation. (see more) |
recovery | Recovery refers to handling invalid syntax during parsing (e.g. a missing comma) and continuing to parse the AST. This avoid showing spurious errors to the user (e.g. showing 'missing field' errors when the struct definition contains errors). |
region | Another term for "lifetime" often used in the literature and in the borrow checker. |
rib | A data structure in the name resolver that keeps track of a single scope for names. (see more) |
RPIT | A return-position impl Trait . (see the reference). |
RPITIT | A return-position impl Trait in trait. Unlike RPIT, this is desugared to a generic associated type (GAT). Introduced in RFC 3425. (see more) |
scrutinee | A scrutinee is the expression that is matched on in match expressions and similar pattern matching constructs. For example, in match x { A => 1, B => 2 } , the expression x is the scrutinee. |
sess | The compiler session, which stores global data used throughout compilation |
side tables | Because the AST and HIR are immutable once created, we often carry extra information about them in the form of hashtables, indexed by the id of a particular node. |
sigil | Like a keyword but composed entirely of non-alphanumeric tokens. For example, & is a sigil for references. |
soundness | A technical term in type theory. Roughly, if a type system is sound, then a program that type-checks is type-safe. That is, one can never (in safe rust) force a value into a variable of the wrong type. (see "completeness"). |
span | A location in the user's source code, used for error reporting primarily. These are like a file-name/line-number/column tuple on steroids: they carry a start/end point, and also track macro expansions and compiler desugaring. All while being packed into a few bytes (really, it's an index into a table). See the Span datatype for more. |
subst | The act of substituting the generic parameters inside of a type, constant expression, etc. with concrete generic arguments by supplying substs. Nowadays referred to as instantiating in the compiler. |
substs | The substitutions for a given generic item (e.g. the i32 , u32 in HashMap<i32, u32> ). Nowadays referred to as the list of generic arguments in the compiler (but note that strictly speaking these two concepts differ, see the literature). |
sysroot | The directory for build artifacts that are loaded by the compiler at runtime. (see more) |
tag | The "tag" of an enum/generator encodes the discriminant of the active variant/state. Tags can either be "direct" (simply storing the discriminant in a field) or use a "niche". |
TAIT | A type-alias impl Trait . Introduced in RFC 2515. |
tcx | Standard variable name for the "typing context" (TyCtxt ), main data structure of the compiler. (see more) |
'tcx | The lifetime of the allocation arenas used by TyCtxt . Most data interned during a compilation session will use this lifetime with the exception of HIR data which uses the 'hir lifetime. (see more) |
token | The smallest unit of parsing. Tokens are produced after lexing (see more). |
TLS | Thread-local storage. Variables may be defined so that each thread has its own copy (rather than all threads sharing the variable). This has some interactions with LLVM. Not all platforms support TLS. |
trait reference, trait ref | The name of a trait along with a suitable list of generic arguments. (see more) |
trans | Short for translation, the code to translate MIR into LLVM IR. Renamed to codegen. |
Ty | The internal representation of a type. (see more) |
TyCtxt | The data structure often referred to as tcx in code which provides access to session data and the query system. |
UFCS | Short for universal function call syntax, this is an unambiguous syntax for calling a method. Term no longer in use! Prefer fully-qualified path/syntax. (see more, see the reference) |
uninhabited type | A type which has no values. This is not the same as a ZST, which has exactly 1 value. An example of an uninhabited type is enum Foo {} , which has no variants, and so, can never be created. The compiler can treat code that deals with uninhabited types as dead code, since there is no such value to be manipulated. ! (the never type) is an uninhabited type. Uninhabited types are also called empty types. |
upvar | A variable captured by a closure from outside the closure. |
variance | Determines how changes to a generic parameter affect subtyping; for example, if T is a subtype of U , then Vec<T> is a subtype Vec<U> because Vec is covariant in its generic parameter. See the background chapter for a more general explanation. See the variance chapter for an explanation of how type checking handles variance. |
variant index | In an enum, identifies a variant by assigning them indices starting at 0. This is purely internal and not to be confused with the "discriminant" which can be overwritten by the user (e.g. enum Bool { True = 42, False = 0 } ). |
well-formedness | Semantically: An expression that evaluates to meaningful result. In type systems: A type related construct which follows rules of the type system. |
wide pointer | A pointer with additional metadata. See fat pointer for more. |
ZST | Zero-sized type. A type whose values have size 0 bytes. Since 2^0 = 1 , such types can have exactly one value. For example, () (unit) is a ZST. struct Foo; is also a ZST. The compiler can do some nice optimizations around ZSTs. |
What's a project without a sense of humor? And frankly some of these are +enlightening?
+ + +Inline assembly in rustc mostly revolves around taking an asm!
macro invocation and plumbing it
+through all of the compiler layers down to LLVM codegen. Throughout the various stages, an
+InlineAsm
generally consists of 3 components:
The template string, which is stored as an array of InlineAsmTemplatePiece
. Each piece
+represents either a literal or a placeholder for an operand (just like format strings).
+#![allow(unused)] +fn main() { +pub enum InlineAsmTemplatePiece { + String(String), + Placeholder { operand_idx: usize, modifier: Option<char>, span: Span }, +} +} +
The list of operands to the asm!
(in
, [late]out
, in[late]out
, sym
, const
). These are
+represented differently at each stage of lowering, but follow a common pattern:
in
, out
and inout
all have an associated register class (reg
) or explicit register
+("eax"
).inout
has 2 forms: one with a single expression that is both read from and written to, and
+one with two separate expressions for the input and output parts.out
and inout
have a late
flag (lateout
/ inlateout
) to indicate that the register
+allocator is allowed to reuse an input register for this output.out
and the split variant of inout
allow _
to be specified for an output, which means
+that the output is discarded. This is used to allocate scratch registers for assembly code.const
refers to an anonymous constants and generally works like an inline const.sym
is a bit special since it only accepts a path expression, which must point to a static
+or a fn
.The options set at the end of the asm!
macro. The only ones that are of particular interest to
+rustc are NORETURN
which makes asm!
return !
instead of ()
, and RAW
which disables format
+string parsing. The remaining options are mostly passed through to LLVM with little processing.
+#![allow(unused)] +fn main() { +bitflags::bitflags! { + pub struct InlineAsmOptions: u16 { + const PURE = 1 << 0; + const NOMEM = 1 << 1; + const READONLY = 1 << 2; + const PRESERVES_FLAGS = 1 << 3; + const NORETURN = 1 << 4; + const NOSTACK = 1 << 5; + const ATT_SYNTAX = 1 << 6; + const RAW = 1 << 7; + const MAY_UNWIND = 1 << 8; + } +} +} +
InlineAsm
is represented as an expression in the AST:
+#![allow(unused)] +fn main() { +pub struct InlineAsm { + pub template: Vec<InlineAsmTemplatePiece>, + pub template_strs: Box<[(Symbol, Option<Symbol>, Span)]>, + pub operands: Vec<(InlineAsmOperand, Span)>, + pub clobber_abi: Option<(Symbol, Span)>, + pub options: InlineAsmOptions, + pub line_spans: Vec<Span>, +} + +pub enum InlineAsmRegOrRegClass { + Reg(Symbol), + RegClass(Symbol), +} + +pub enum InlineAsmOperand { + In { + reg: InlineAsmRegOrRegClass, + expr: P<Expr>, + }, + Out { + reg: InlineAsmRegOrRegClass, + late: bool, + expr: Option<P<Expr>>, + }, + InOut { + reg: InlineAsmRegOrRegClass, + late: bool, + expr: P<Expr>, + }, + SplitInOut { + reg: InlineAsmRegOrRegClass, + late: bool, + in_expr: P<Expr>, + out_expr: Option<P<Expr>>, + }, + Const { + anon_const: AnonConst, + }, + Sym { + expr: P<Expr>, + }, +} +} +
The asm!
macro is implemented in rustc_builtin_macros
and outputs an InlineAsm
AST node. The
+template string is parsed using fmt_macros
, positional and named operands are resolved to
+explicit operand indices. Since target information is not available to macro invocations,
+validation of the registers and register classes is deferred to AST lowering.
InlineAsm
is represented as an expression in the HIR:
+#![allow(unused)] +fn main() { +pub struct InlineAsm<'hir> { + pub template: &'hir [InlineAsmTemplatePiece], + pub template_strs: &'hir [(Symbol, Option<Symbol>, Span)], + pub operands: &'hir [(InlineAsmOperand<'hir>, Span)], + pub options: InlineAsmOptions, + pub line_spans: &'hir [Span], +} + +pub enum InlineAsmRegOrRegClass { + Reg(InlineAsmReg), + RegClass(InlineAsmRegClass), +} + +pub enum InlineAsmOperand<'hir> { + In { + reg: InlineAsmRegOrRegClass, + expr: Expr<'hir>, + }, + Out { + reg: InlineAsmRegOrRegClass, + late: bool, + expr: Option<Expr<'hir>>, + }, + InOut { + reg: InlineAsmRegOrRegClass, + late: bool, + expr: Expr<'hir>, + }, + SplitInOut { + reg: InlineAsmRegOrRegClass, + late: bool, + in_expr: Expr<'hir>, + out_expr: Option<Expr<'hir>>, + }, + Const { + anon_const: AnonConst, + }, + Sym { + expr: Expr<'hir>, + }, +} +} +
AST lowering is where InlineAsmRegOrRegClass
is converted from Symbol
s to an actual register or
+register class. If any modifiers are specified for a template string placeholder, these are
+validated against the set allowed for that operand type. Finally, explicit registers for inputs and
+outputs are checked for conflicts (same register used for different operands).
Each register class has a whitelist of types that it may be used with. After the types of all
+operands have been determined, the intrinsicck
pass will check that these types are in the
+whitelist. It also checks that split inout
operands have compatible types and that const
+operands are integers or floats. Suggestions are emitted where needed if a template modifier should
+be used for an operand based on the type that was passed into it.
InlineAsm
is represented as an expression in the THIR:
+#![allow(unused)] +fn main() { +crate enum ExprKind<'tcx> { + // [..] + InlineAsm { + template: &'tcx [InlineAsmTemplatePiece], + operands: Box<[InlineAsmOperand<'tcx>]>, + options: InlineAsmOptions, + line_spans: &'tcx [Span], + }, +} +crate enum InlineAsmOperand<'tcx> { + In { + reg: InlineAsmRegOrRegClass, + expr: ExprId, + }, + Out { + reg: InlineAsmRegOrRegClass, + late: bool, + expr: Option<ExprId>, + }, + InOut { + reg: InlineAsmRegOrRegClass, + late: bool, + expr: ExprId, + }, + SplitInOut { + reg: InlineAsmRegOrRegClass, + late: bool, + in_expr: ExprId, + out_expr: Option<ExprId>, + }, + Const { + value: &'tcx Const<'tcx>, + span: Span, + }, + SymFn { + expr: ExprId, + }, + SymStatic { + def_id: DefId, + }, +} +} +
The only significant change compared to HIR is that Sym
has been lowered to either a SymFn
+whose expr
is a Literal
ZST of the fn
, or a SymStatic
which points to the DefId
of a
+static
.
InlineAsm
is represented as a Terminator
in the MIR:
+#![allow(unused)] +fn main() { +pub enum TerminatorKind<'tcx> { + // [..] + + /// Block ends with an inline assembly block. This is a terminator since + /// inline assembly is allowed to diverge. + InlineAsm { + /// The template for the inline assembly, with placeholders. + template: &'tcx [InlineAsmTemplatePiece], + + /// The operands for the inline assembly, as `Operand`s or `Place`s. + operands: Vec<InlineAsmOperand<'tcx>>, + + /// Miscellaneous options for the inline assembly. + options: InlineAsmOptions, + + /// Source spans for each line of the inline assembly code. These are + /// used to map assembler errors back to the line in the source code. + line_spans: &'tcx [Span], + + /// Destination block after the inline assembly returns, unless it is + /// diverging (InlineAsmOptions::NORETURN). + destination: Option<BasicBlock>, + }, +} + +pub enum InlineAsmOperand<'tcx> { + In { + reg: InlineAsmRegOrRegClass, + value: Operand<'tcx>, + }, + Out { + reg: InlineAsmRegOrRegClass, + late: bool, + place: Option<Place<'tcx>>, + }, + InOut { + reg: InlineAsmRegOrRegClass, + late: bool, + in_value: Operand<'tcx>, + out_place: Option<Place<'tcx>>, + }, + Const { + value: Box<Constant<'tcx>>, + }, + SymFn { + value: Box<Constant<'tcx>>, + }, + SymStatic { + def_id: DefId, + }, +} +} +
As part of THIR lowering, InOut
and SplitInOut
operands are lowered to a split form with a
+separate in_value
and out_place
.
Semantically, the InlineAsm
terminator is similar to the Call
terminator except that it has
+multiple output places where a Call
only has a single return place output.
Operands are lowered one more time before being passed to LLVM codegen:
++#![allow(unused)] +fn main() { +pub enum InlineAsmOperandRef<'tcx, B: BackendTypes + ?Sized> { + In { + reg: InlineAsmRegOrRegClass, + value: OperandRef<'tcx, B::Value>, + }, + Out { + reg: InlineAsmRegOrRegClass, + late: bool, + place: Option<PlaceRef<'tcx, B::Value>>, + }, + InOut { + reg: InlineAsmRegOrRegClass, + late: bool, + in_value: OperandRef<'tcx, B::Value>, + out_place: Option<PlaceRef<'tcx, B::Value>>, + }, + Const { + string: String, + }, + SymFn { + instance: Instance<'tcx>, + }, + SymStatic { + def_id: DefId, + }, +} +} +
The operands are lowered to LLVM operands and constraint codes as follow:
+out
and the output part of inout
operands are added first, as required by LLVM. Late output
+operands have a =
prefix added to their constraint code, non-late output operands have a =&
+prefix added to their constraint code.in
operands are added normally.inout
operands are tied to the matching output operand.sym
operands are passed as function pointers or pointers, using the "s"
constraint.const
operands are formatted to a string and directly inserted in the template string.The template string is converted to LLVM form:
+$
characters are escaped as $$
.const
operands are converted to strings and inserted directly.${X:M}
where X
is the operand index and M
is the modifier
+character. Modifiers are converted from the Rust form to the LLVM form.The various options are converted to clobber constraints or LLVM attributes, refer to the +RFC +for more details.
+Note that LLVM is sometimes rather picky about what types it accepts for certain constraint codes +so we sometimes need to insert conversions to/from a supported type. See the target-specific +ISelLowering.cpp files in LLVM for details of what types are supported for each register class.
+Adding inline assembly support to an architecture is mostly a matter of defining the registers and
+register classes for that architecture. All the definitions for register classes are located in
+compiler/rustc_target/asm/
.
Additionally you will need to implement lowering of these register classes to LLVM constraint codes
+in compiler/rustc_codegen_llvm/asm.rs
.
When adding a new architecture, make sure to cross-reference with the LLVM source code:
+getRegForInlineAsmConstraint
function in lib/Target/${ARCH}/${ARCH}ISelLowering.cpp
.getReservedRegs
+function in lib/Target/${ARCH}/${ARCH}RegisterInfo.cpp
. Any "conditionally" reserved register
+such as the frame/base pointer must always be treated as reserved for Rust purposes because we
+can't know ahead of time whether a function will require a frame/base pointer.Various tests for inline assembly are available:
+tests/assembly/asm
tests/ui/asm
tests/codegen/asm-*
Every architecture supported by inline assembly must have exhaustive tests in
+tests/assembly/asm
which test all combinations of register classes and types.
The AST lowering step converts AST to HIR. +This means many structures are removed if they are irrelevant +for type analysis or similar syntax agnostic analyses. Examples +of such structures include but are not limited to
+for
loops and while (let)
loops
+loop
+ match
and some let
bindingsif let
+match
impl Trait
+impl Trait
+existential type
declarationLowering needs to uphold several invariants in order to not trigger the
+sanity checks in compiler/rustc_passes/src/hir_id_validator.rs
:
HirId
must be used if created. So if you use the lower_node_id
,
+you must use the resulting NodeId
or HirId
(either is fine, since
+any NodeId
s in the HIR
are checked for existing HirId
s)HirId
must be done in the scope of the owning item.
+This means you need to use with_hir_id_owner
if you are creating parts
+of an item other than the one being currently lowered. This happens for
+example during the lowering of existential impl Trait
NodeId
that will be placed into a HIR structure must be lowered,
+even if its HirId
is unused. Calling
+let _ = self.lower_node_id(node_id);
is perfectly legitimate.AST
, you must
+create new ids for them. This is done by calling the next_id
method,
+which produces both a new NodeId
as well as automatically lowering it
+for you so you also get the HirId
.If you are creating new DefId
s, since each DefId
needs to have a
+corresponding NodeId
, it is advisable to add these NodeId
s to the
+AST
so you don't have to generate new ones during lowering. This has
+the advantage of creating a way to find the DefId
of something via its
+NodeId
. If lowering needs this DefId
in multiple places, you can't
+generate a new NodeId
in all those places because you'd also get a new
+DefId
then. With a NodeId
from the AST
this is not an issue.
Having the NodeId
also allows the DefCollector
to generate the DefId
s
+instead of lowering having to do it on the fly. Centralizing the DefId
+generation in one place makes it easier to refactor and reason about.
AST validation is a separate AST pass that visits each +item in the tree and performs simple checks. This pass +doesn't perform any complex analysis, type checking or +name resolution.
+Before performing any validation, the compiler first expands +the macros. Then this pass performs validations to check +that each AST item is in the correct state. And when this pass +is done, the compiler runs the crate resolution pass.
+Validations are defined in AstValidator
type, which
+itself is located in rustc_ast_passes
crate. This
+type implements various simple checks which emit errors
+when certain language rules are broken.
In addition, AstValidator
implements Visitor
trait
+that defines how to visit AST items (which can be functions,
+traits, enums, etc).
For each item, visitor performs specific checks. For
+example, when visiting a function declaration,
+AstValidator
checks that the function has:
u16::MAX
parameters;Attributes come in two types: inert (or built-in) and active (non-builtin).
+These attributes are defined in the compiler itself, in
+compiler/rustc_feature/src/builtin_attrs.rs
.
Examples include #[allow]
and #[macro_use]
.
These attributes have several important characteristics:
+use allow as foo
will compile, but writing #[foo]
will
+produce an error.#[allow]
, #[warn]
, #[deny]
, and
+#[forbid]
, rather than the behavior coming from the expansion of the attributes themselves.These attributes are defined by a crate - either the standard library, or a proc-macro crate.
+Important: Many non-builtin attributes, such as #[derive]
, are still considered part of the
+core Rust language. However, they are not called 'builtin attributes', since they have a
+corresponding definition in the standard library.
Definitions of non-builtin attributes take two forms:
+#[proc_macro_attribute]
in a
+proc-macro crate.library/core/src/macros/mod.rs
.These definitions exist to allow the macros to participate in typical path-based resolution - they
+can be imported, re-exported, and renamed just like any other item definition. However, the body of
+the definition is empty. Instead, the macro is annotated with the #[rustc_builtin_macro]
+attribute, which tells the compiler to run a corresponding function in rustc_builtin_macros
.
All non-builtin attributes have the following characteristics:
+#[derive]
+works without an import.rustc_codegen_ssa
+provides an abstract interface for all backends to implement,
+namely LLVM, Cranelift, and GCC.
Below is some background information on the refactoring that created this +abstract interface.
+rustc_codegen_llvm
by Denis Merigoux, October 23rd 2018
+All the code related to the compilation of MIR into LLVM IR was contained
+inside the rustc_codegen_llvm
crate. Here is the breakdown of the most
+important elements:
back
folder (7,800 LOC) implements the mechanisms for creating the
+different object files and archive through LLVM, but also the communication
+mechanisms for parallel code generation;debuginfo
(3,200 LOC) folder contains all code that passes debug
+information down to LLVM;llvm
(2,200 LOC) folder defines the FFI necessary to communicate with
+LLVM using the C++ API;mir
(4,300 LOC) folder implements the actual lowering from MIR to LLVM
+IR;base.rs
(1,300 LOC) file contains some helper functions but also the
+high-level code that launches the code generation and distributes the work.builder.rs
(1,200 LOC) file contains all the functions generating
+individual LLVM IR instructions inside a basic block;common.rs
(450 LOC) contains various helper functions and all the
+functions generating LLVM static values;type_.rs
(300 LOC) defines most of the type translations to LLVM IR.The goal of this refactoring is to separate inside this crate code that is
+specific to the LLVM from code that can be reused for other rustc backends. For
+instance, the mir
folder is almost entirely backend-specific but it relies
+heavily on other parts of the crate. The separation of the code must not affect
+the logic of the code nor its performance.
For these reasons, the separation process involves two transformations that +have to be done at the same time for the resulting code to compile :
+While the LLVM-specific code will be left in rustc_codegen_llvm
, all the new
+traits and backend-agnostic code will be moved in rustc_codegen_ssa
(name
+suggestion by @eddyb).
@irinagpopa started to parametrize the types of rustc_codegen_llvm
by a
+generic Value
type, implemented in LLVM by a reference &'ll Value
. This
+work has been extended to all structures inside the mir
folder and elsewhere,
+as well as for LLVM's BasicBlock
and Type
types.
The two most important structures for the LLVM codegen are CodegenCx
and
+Builder
. They are parametrized by multiple lifetime parameters and the type
+for Value
.
struct CodegenCx<'ll, 'tcx> {
+ /* ... */
+}
+
+struct Builder<'a, 'll, 'tcx> {
+ cx: &'a CodegenCx<'ll, 'tcx>,
+ /* ... */
+}
+
+CodegenCx
is used to compile one codegen-unit that can contain multiple
+functions, whereas Builder
is created to compile one basic block.
The code in rustc_codegen_llvm
has to deal with multiple explicit lifetime
+parameters, that correspond to the following:
'tcx
is the longest lifetime, that corresponds to the original TyCtxt
+containing the program's information;'a
is a short-lived reference of a CodegenCx
or another object inside a
+struct;'ll
is the lifetime of references to LLVM objects such as Value
or
+Type
.Although there are already many lifetime parameters in the code, making it
+generic uncovered situations where the borrow-checker was passing only due to
+the special nature of the LLVM objects manipulated (they are extern pointers).
+For instance, an additional lifetime parameter had to be added to
+LocalAnalyser
in analyse.rs
, leading to the definition:
struct LocalAnalyzer<'mir, 'a, 'tcx> {
+ /* ... */
+}
+
+However, the two most important structures CodegenCx
and Builder
are not
+defined in the backend-agnostic code. Indeed, their content is highly specific
+of the backend and it makes more sense to leave their definition to the backend
+implementor than to allow just a narrow spot via a generic field for the
+backend's context.
Because they have to be defined by the backend, CodegenCx
and Builder
will
+be the structures implementing all the traits defining the backend's interface.
+These traits are defined in the folder rustc_codegen_ssa/traits
and all the
+backend-agnostic code is parametrized by them. For instance, let us explain how
+a function in base.rs
is parametrized:
pub fn codegen_instance<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
+ cx: &'a Bx::CodegenCx,
+ instance: Instance<'tcx>
+) {
+ /* ... */
+}
+
+In this signature, we have the two lifetime parameters explained earlier and
+the master type Bx
which satisfies the trait BuilderMethods
corresponding
+to the interface satisfied by the Builder
struct. The BuilderMethods
+defines an associated type Bx::CodegenCx
that itself satisfies the
+CodegenMethods
traits implemented by the struct CodegenCx
.
On the trait side, here is an example with part of the definition of
+BuilderMethods
in traits/builder.rs
:
pub trait BuilderMethods<'a, 'tcx>:
+ HasCodegen<'tcx>
+ + DebugInfoBuilderMethods<'tcx>
+ + ArgTypeMethods<'tcx>
+ + AbiBuilderMethods<'tcx>
+ + IntrinsicCallMethods<'tcx>
+ + AsmBuilderMethods<'tcx>
+{
+ fn new_block<'b>(
+ cx: &'a Self::CodegenCx,
+ llfn: Self::Function,
+ name: &'b str
+ ) -> Self;
+ /* ... */
+ fn cond_br(
+ &mut self,
+ cond: Self::Value,
+ then_llbb: Self::BasicBlock,
+ else_llbb: Self::BasicBlock,
+ );
+ /* ... */
+}
+
+Finally, a master structure implementing the ExtraBackendMethods
trait is
+used for high-level codegen-driving functions like codegen_crate
in
+base.rs
. For LLVM, it is the empty LlvmCodegenBackend
.
+ExtraBackendMethods
should be implemented by the same structure that
+implements the CodegenBackend
defined in
+rustc_codegen_utils/codegen_backend.rs
.
During the traitification process, certain functions have been converted from
+methods of a local structure to methods of CodegenCx
or Builder
and a
+corresponding self
parameter has been added. Indeed, LLVM stores information
+internally that it can access when called through its API. This information
+does not show up in a Rust data structure carried around when these methods are
+called. However, when implementing a Rust backend for rustc
, these methods
+will need information from CodegenCx
, hence the additional parameter (unused
+in the LLVM implementation of the trait).
The traits offer an API which is very similar to the API of LLVM. This is not +the best solution since LLVM has a very special way of doing things: when +adding another backend, the traits definition might be changed in order to +offer more flexibility.
+However, the current separation between backend-agnostic and LLVM-specific code
+has allowed the reuse of a significant part of the old rustc_codegen_llvm
.
+Here is the new LOC breakdown between backend-agnostic (BA) and LLVM for the
+most important elements:
back
folder: 3,800 (BA) vs 4,100 (LLVM);mir
folder: 4,400 (BA) vs 0 (LLVM);base.rs
: 1,100 (BA) vs 250 (LLVM);builder.rs
: 1,400 (BA) vs 0 (LLVM);common.rs
: 350 (BA) vs 350 (LLVM);The debuginfo
folder has been left almost untouched by the splitting and is
+specific to LLVM. Only its high-level features have been traitified.
The new traits
folder has 1500 LOC only for trait definitions. Overall, the
+27,000 LOC-sized old rustc_codegen_llvm
code has been split into the new
+18,500 LOC-sized new rustc_codegen_llvm
and the 12,000 LOC-sized
+rustc_codegen_ssa
. We can say that this refactoring allowed the reuse of
+approximately 10,000 LOC that would otherwise have had to be duplicated between
+the multiple backends of rustc
.
The refactored version of rustc
's backend introduced no regression over the
+test suite nor in performance benchmark, which is in coherence with the nature
+of the refactoring that used only compile-time parametricity (no trait
+objects).
Code generation (or "codegen") is the part of the compiler
+that actually generates an executable binary.
+Usually, rustc uses LLVM for code generation,
+but there is also support for Cranelift and GCC.
+The key is that rustc doesn't implement codegen itself.
+It's worth noting, though, that in the Rust source code,
+many parts of the backend have codegen
in their names
+(there are no hard boundaries).
++NOTE: If you are looking for hints on how to debug code generation bugs, +please see this section of the debugging chapter.
+
LLVM is "a collection of modular and reusable compiler and
+toolchain technologies". In particular, the LLVM project contains a pluggable
+compiler backend (also called "LLVM"), which is used by many compiler projects,
+including the clang
C compiler and our beloved rustc
.
LLVM takes input in the form of LLVM IR. It is basically assembly code with +additional low-level types and annotations added. These annotations are helpful +for doing optimizations on the LLVM IR and outputted machine code. The end +result of all this is (at long last) something executable (e.g. an ELF object, +an EXE, or wasm).
+There are a few benefits to using LLVM:
+Once LLVM IR for all of the functions and statics, etc is built, it is time to +start running LLVM and its optimization passes. LLVM IR is grouped into +"modules". Multiple "modules" can be codegened at the same time to aid in +multi-core utilization. These "modules" are what we refer to as codegen +units. These units were established way back during monomorphization +collection phase.
+Once LLVM produces objects from these modules, these objects are passed to the +linker along with, optionally, the metadata object and an archive or an +executable is produced.
+It is not necessarily the codegen phase described above that runs the +optimizations. With certain kinds of LTO, the optimization might happen at the +linking time instead. It is also possible for some optimizations to happen +before objects are passed on to the linker and some to happen during the +linking.
+This all happens towards the very end of compilation. The code for this can be
+found in rustc_codegen_ssa::back
and
+rustc_codegen_llvm::back
. Sadly, this piece of code is not
+really well-separated into LLVM-dependent code; the rustc_codegen_ssa
+contains a fair amount of code specific to the LLVM backend.
Once these components are done with their work you end up with a number of +files in your filesystem corresponding to the outputs you have requested.
+ +++NOTE: If you are looking for info about code generation, please see this +chapter instead.
+
This section is about debugging compiler bugs in code generation (e.g. why the +compiler generated some piece of code or crashed in LLVM). LLVM is a big +project on its own that probably needs to have its own debugging document (not +that I could find one). But here are some tips that are important in a rustc +context:
+As a general rule, compilers generate lots of information from analyzing code. +Thus, a useful first step is usually to find a minimal example. One way to do +this is to
+create a new crate that reproduces the issue (e.g. adding whatever crate is +at fault as a dependency, and using it from there)
+minimize the crate by removing external dependencies; that is, moving +everything relevant to the new crate
+further minimize the issue by making the code shorter (there are tools that
+help with this like creduce
)
For more discussion on methodology for steps 2 and 3 above, there is an +epic blog post from pnkfelix specifically about Rust program minimization.
+The official compilers (including nightlies) have LLVM assertions disabled,
+which means that LLVM assertion failures can show up as compiler crashes (not
+ICEs but "real" crashes) and other sorts of weird behavior. If you are
+encountering these, it is a good idea to try using a compiler with LLVM
+assertions enabled - either an "alt" nightly or a compiler you build yourself
+by setting [llvm] assertions=true
in your config.toml - and see whether
+anything turns up.
The rustc build process builds the LLVM tools into
+./build/<host-triple>/llvm/bin
. They can be called directly.
+These tools include:
llc
, which compiles bitcode (.bc
files) to executable code; this can be used to
+replicate LLVM backend bugs.opt
, a bitcode transformer that runs LLVM optimization passes.bugpoint
, which reduces large test cases to small, useful ones.By default, the Rust build system does not check for changes to the LLVM source code or
+its build configuration settings. So, if you need to rebuild the LLVM that is linked
+into rustc
, first delete the file llvm-finished-building
, which should be located
+in build/<host-triple>/llvm/
.
The default rustc compilation pipeline has multiple codegen units, which is
+hard to replicate manually and means that LLVM is called multiple times in
+parallel. If you can get away with it (i.e. if it doesn't make your bug
+disappear), passing -C codegen-units=1
to rustc will make debugging easier.
For rustc to generate LLVM IR, you need to pass the --emit=llvm-ir
flag. If
+you are building via cargo, use the RUSTFLAGS
environment variable (e.g.
+RUSTFLAGS='--emit=llvm-ir'
). This causes rustc to spit out LLVM IR into the
+target directory.
cargo llvm-ir [options] path
spits out the LLVM IR for a particular function
+at path
. (cargo install cargo-asm
installs cargo asm
and cargo llvm-ir
). --build-type=debug
emits code for debug builds. There are also
+other useful options. Also, debug info in LLVM IR can clutter the output a lot:
+RUSTFLAGS="-C debuginfo=0"
is really useful.
RUSTFLAGS="-C save-temps"
outputs LLVM bitcode (not the same as IR) at
+different stages during compilation, which is sometimes useful. The output LLVM
+bitcode will be in .bc
files in the compiler's output directory, set via the
+--out-dir DIR
argument to rustc
.
If you are hitting an assertion failure or segmentation fault from the LLVM
+backend when invoking rustc
itself, it is a good idea to try passing each
+of these .bc
files to the llc
command, and see if you get the same
+failure. (LLVM developers often prefer a bug reduced to a .bc
file over one
+that uses a Rust crate for its minimized reproduction.)
To get human readable versions of the LLVM bitcode, one just needs to convert
+the bitcode (.bc
) files to .ll
files using llvm-dis
, which should be in
+the target local compilation of rustc.
Note that rustc emits different IR depending on whether -O
is enabled, even
+without LLVM's optimizations, so if you want to play with the IR rustc emits,
+you should:
$ rustc +local my-file.rs --emit=llvm-ir -O -C no-prepopulate-passes \
+ -C codegen-units=1
+$ OPT=./build/$TRIPLE/llvm/bin/opt
+$ $OPT -S -O2 < my-file.ll > my
+
+If you just want to get the LLVM IR during the LLVM pipeline, to e.g. see which
+IR causes an optimization-time assertion to fail, or to see when LLVM performs
+a particular optimization, you can pass the rustc flag -C llvm-args=-print-after-all
, and possibly add -C llvm-args='-filter-print-funcs=EXACT_FUNCTION_NAME
(e.g. -C llvm-args='-filter-print-funcs=_ZN11collections3str21_$LT$impl$u20$str$GT$\ 7replace17hbe10ea2e7c809b0bE'
).
That produces a lot of output into standard error, so you'll want to pipe that
+to some file. Also, if you are using neither -filter-print-funcs
nor -C codegen-units=1
, then, because the multiple codegen units run in parallel, the
+printouts will mix together and you won't be able to read anything.
One caveat to the aforementioned methodology: the -print
family of options
+to LLVM only prints the IR unit that the pass runs on (e.g., just a
+function), and does not include any referenced declarations, globals,
+metadata, etc. This means you cannot in general feed the output of -print
+into llc
to reproduce a given problem.
Within LLVM itself, calling F.getParent()->dump()
at the beginning of
+SafeStackLegacyPass::runOnFunction
will dump the whole module, which
+may provide better basis for reproduction. (However, you
+should be able to get that same dump from the .bc
files dumped by
+-C save-temps
.)
If you want just the IR for a specific function (say, you want to see why it
+causes an assertion or doesn't optimize correctly), you can use llvm-extract
,
+e.g.
$ ./build/$TRIPLE/llvm/bin/llvm-extract \
+ -func='_ZN11collections3str21_$LT$impl$u20$str$GT$7replace17hbe10ea2e7c809b0bE' \
+ -S \
+ < unextracted.ll \
+ > extracted.ll
+
+If you are seeing incorrect behavior due to an optimization pass, a very handy
+LLVM option is -opt-bisect-limit
, which takes an integer denoting the index
+value of the highest pass to run. Index values for taken passes are stable
+from run to run; by coupling this with software that automates bisecting the
+search space based on the resulting program, an errant pass can be quickly
+determined. When an -opt-bisect-limit
is specified, all runs are displayed
+to standard error, along with their index and output indicating if the
+pass was run or skipped. Setting the limit to an index of -1 (e.g.,
+RUSTFLAGS="-C llvm-args=-opt-bisect-limit=-1"
) will show all passes and
+their corresponding index values.
If you want to play with the optimization pipeline, you can use the opt
tool
+from ./build/<host-triple>/llvm/bin/
with the LLVM IR emitted by rustc.
When investigating the implementation of LLVM itself, you should be +aware of its internal debug infrastructure. +This is provided in LLVM Debug builds, which you enable for rustc +LLVM builds by changing this setting in the config.toml:
+[llvm]
+# Indicates whether the LLVM assertions are enabled or not
+assertions = true
+
+# Indicates whether the LLVM build is a Release or Debug build
+optimize = false
+
+The quick summary is:
+assertions=true
enables coarse-grain debug messaging.
+optimize=false
enables fine-grain debug messaging.LLVM_DEBUG(dbgs() << msg)
in LLVM is like debug!(msg)
in rustc
.-debug
option turns on all messaging; it is like setting the
+environment variable RUSTC_LOG=debug
in rustc
.-debug-only=<pass1>,<pass2>
variant is more selective; it is like
+setting the environment variable RUSTC_LOG=path1,path2
in rustc
.If you have some questions, head over to the rust-lang Zulip and
+specifically the #t-compiler/wg-llvm
stream.
The -C help
and -Z help
compiler switches will list out a variety
+of interesting options you may find useful. Here are a few of the most
+common that pertain to LLVM development (some of them are employed in the
+tutorial above):
--emit llvm-ir
option emits a <filename>.ll
file with LLVM IR in textual format
+--emit llvm-bc
option emits in bytecode format (<filename>.bc
)-C llvm-args=<foo>
allows passing pretty much all the
+options that tools like llc and opt would accept;
+e.g. -C llvm-args=-print-before-all
to print IR before every LLVM
+pass.-C no-prepopulate-passes
will avoid pre-populate the LLVM pass
+manager with a list of passes. This will allow you to view the LLVM
+IR that rustc generates, not the LLVM IR after optimizations.-C passes=val
option allows you to supply a space separated list of extra LLVM passes to run-C save-temps
option saves all temporary output files during compilation-Z print-llvm-passes
option will print out LLVM optimization passes being run-Z time-llvm-passes
option measures the time of each LLVM pass-Z verify-llvm-ir
option will verify the LLVM IR for correctness-Z no-parallel-backend
will disable parallel compilation of distinct compilation units-Z llvm-time-trace
option will output a Chrome profiler compatible JSON file
+which contains details and timings for LLVM passes.-C llvm-args=-opt-bisect-limit=<index>
option allows for bisecting LLVM
+optimizations.When filing an LLVM bug report, you will probably want some sort of minimal +working example that demonstrates the problem. The Godbolt compiler explorer is +really helpful for this.
+Once you have some LLVM IR for the problematic code (see above), you can +create a minimal working example with Godbolt. Go to +llvm.godbolt.org.
+Choose LLVM-IR
as programming language.
Use llc
to compile the IR to a particular target as is:
-mattr
enables target features, -march=
+selects the target, -mcpu=
selects the CPU, etc.llc -march=help
output all architectures available, which
+is useful because sometimes the Rust arch names and the LLVM names do not
+match.llc
, opt
, etc.If you want to optimize the LLVM-IR, you can use opt
to see how the LLVM
+optimizations transform it.
Once you have a godbolt link demonstrating the issue, it is pretty easy to +fill in an LLVM bug. Just visit their github issues page.
+Once you've identified the bug as an LLVM bug, you will sometimes +find that it has already been reported and fixed in LLVM, but we haven't +gotten the fix yet (or perhaps you are familiar enough with LLVM to fix it yourself).
+In that case, we can sometimes opt to port the fix for the bug +directly to our own LLVM fork, so that rustc can use it more easily. +Our fork of LLVM is maintained in rust-lang/llvm-project. Once +you've landed the fix there, you'll also need to land a PR modifying +our submodule commits -- ask around on Zulip for help.
+ +const
+
+#[track_caller]
callees
+
+Approved in RFC 2091, this feature enables the accurate reporting of caller location during panics
+initiated from functions like Option::unwrap
, Result::expect
, and Index::index
. This feature
+adds the #[track_caller]
attribute for functions, the
+caller_location
intrinsic, and the stabilization-friendly
+core::panic::Location::caller
wrapper.
Take this example program:
++fn main() { + let foo: Option<()> = None; + foo.unwrap(); // this should produce a useful panic message! +} +
Prior to Rust 1.42, panics like this unwrap()
printed a location in core:
$ rustc +1.41.0 example.rs; example.exe
+thread 'main' panicked at 'called `Option::unwrap()` on a `None` value',...core\macros\mod.rs:15:40
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
+
+As of 1.42, we get a much more helpful message:
+$ rustc +1.42.0 example.rs; example.exe
+thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', example.rs:3:5
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
+
+These error messages are achieved through a combination of changes to panic!
internals to make use
+of core::panic::Location::caller
and a number of #[track_caller]
annotations in the standard
+library which propagate caller information.
Previously, panic!
made use of the file!()
, line!()
, and column!()
macros to construct a
+Location
pointing to where the panic occurred. These macros couldn't be given an overridden
+location, so functions which intentionally invoked panic!
couldn't provide their own location,
+hiding the actual source of error.
Internally, panic!()
now calls core::panic::Location::caller()
to find out where it
+was expanded. This function is itself annotated with #[track_caller]
and wraps the
+caller_location
compiler intrinsic implemented by rustc. This intrinsic is easiest
+explained in terms of how it works in a const
context.
const
There are two main phases to returning the caller location in a const context: walking up the stack +to find the right location and allocating a const value to return.
+Location
In a const context we "walk up the stack" from where the intrinsic is invoked, stopping when we
+reach the first function call in the stack which does not have the attribute. This walk is in
+InterpCx::find_closest_untracked_caller_location()
.
Starting at the bottom, we iterate up over stack Frame
s in the
+InterpCx::stack
, calling
+InstanceKind::requires_caller_location
on the
+Instance
s from each Frame
. We stop once we find one that returns false
and
+return the span of the previous frame which was the "topmost" tracked function.
Location
Once we have a Span
, we need to allocate static memory for the Location
, which is performed by
+the TyCtxt::const_caller_location()
query. Internally this calls
+InterpCx::alloc_caller_location()
and results in a unique
+memory kind (MemoryKind::CallerLocation
). The SSA codegen backend is able
+to emit code for these same values, and we use this code there as well.
Once our Location
has been allocated in static memory, our intrinsic returns a reference to it.
#[track_caller]
calleesTo generate efficient code for a tracked function and its callers, we need to provide the same +behavior from the intrinsic's point of view without having a stack to walk up at runtime. We invert +the approach: as we grow the stack down we pass an additional argument to calls of tracked functions +rather than walking up the stack when the intrinsic is called. That additional argument can be +returned wherever the caller location is queried.
+The argument we append is of type &'static core::panic::Location<'static>
. A reference was chosen
+to avoid unnecessary copying because a pointer is a third the size of
+std::mem::size_of::<core::panic::Location>() == 24
at time of writing.
When generating a call to a function which is tracked, we pass the location argument the value of
+FunctionCx::get_caller_location
.
If the calling function is tracked, get_caller_location
returns the local in
+FunctionCx::caller_location
which was populated by the current caller's caller.
+In these cases the intrinsic "returns" a reference which was actually provided in an argument to its
+caller.
If the calling function is not tracked, get_caller_location
allocates a Location
static from
+the current Span
and returns a reference to that.
We more efficiently achieve the same behavior as a loop starting from the bottom by passing a single
+&Location
value through the caller_location
fields of multiple FunctionCx
s as we grow the
+stack downward.
What does this transformation look like in practice? Take this example which uses the new feature:
++#![feature(track_caller)] +use std::panic::Location; + +#[track_caller] +fn print_caller() { + println!("called from {}", Location::caller()); +} + +fn main() { + print_caller(); +} +
Here print_caller()
appears to take no arguments, but we compile it to something like this:
+#![feature(panic_internals)] +use std::panic::Location; + +fn print_caller(caller: &Location) { + println!("called from {}", caller); +} + +fn main() { + print_caller(&Location::internal_constructor(file!(), line!(), column!())); +} +
In codegen contexts we have to modify the callee ABI to pass this information down the stack, but +the attribute expressly does not modify the type of the function. The ABI change must be +transparent to type checking and remain sound in all uses.
+Direct calls to tracked functions will always know the full codegen flags for the callee and can
+generate appropriate code. Indirect callers won't have this information and it's not encoded in
+the type of the function pointer they call, so we generate a ReifyShim
around the function
+whenever taking a pointer to it. This shim isn't able to report the actual location of the indirect
+call (the function's definition site is reported instead), but it prevents miscompilation and is
+probably the best we can do without modifying fully-stabilized type signatures.
++Note: We always emit a
+ReifyShim
when taking a pointer to a tracked function. While the +constraint here is imposed by codegen contexts, we don't know during MIR construction of the shim +whether we'll be called in a const context (safe to ignore shim) or in a codegen context (unsafe +to ignore shim). Even if we did know, the results from const and codegen contexts must agree.
The #[track_caller]
attribute is checked alongside other codegen attributes to ensure the
+function:
"Rust"
ABI (as opposed to e.g., "C"
)#[naked]
If the use is valid, we set CodegenFnAttrsFlags::TRACK_CALLER
. This flag influences
+the return value of InstanceKind::requires_caller_location
which is in turn
+used in both const and codegen contexts to ensure correct propagation.
When applied to trait method implementations, the attribute works as it does for regular functions.
+When applied to a trait method prototype, the attribute applies to all implementations of the +method. When applied to a default trait method implementation, the attribute takes effect on +that implementation and any overrides.
+Examples:
++#![feature(track_caller)] + +macro_rules! assert_tracked { + () => {{ + let location = std::panic::Location::caller(); + assert_eq!(location.file(), file!()); + assert_ne!(location.line(), line!(), "line should be outside this fn"); + println!("called at {}", location); + }}; +} + +trait TrackedFourWays { + /// All implementations inherit `#[track_caller]`. + #[track_caller] + fn blanket_tracked(); + + /// Implementors can annotate themselves. + fn local_tracked(); + + /// This implementation is tracked (overrides are too). + #[track_caller] + fn default_tracked() { + assert_tracked!(); + } + + /// Overrides of this implementation are tracked (it is too). + #[track_caller] + fn default_tracked_to_override() { + assert_tracked!(); + } +} + +/// This impl uses the default impl for `default_tracked` and provides its own for +/// `default_tracked_to_override`. +impl TrackedFourWays for () { + fn blanket_tracked() { + assert_tracked!(); + } + + #[track_caller] + fn local_tracked() { + assert_tracked!(); + } + + fn default_tracked_to_override() { + assert_tracked!(); + } +} + +fn main() { + <() as TrackedFourWays>::blanket_tracked(); + <() as TrackedFourWays>::default_tracked(); + <() as TrackedFourWays>::default_tracked_to_override(); + <() as TrackedFourWays>::local_tracked(); +} +
Broadly speaking, this feature's goal is to improve common Rust error messages without breaking +stability guarantees, requiring modifications to end-user source, relying on platform-specific +debug-info, or preventing user-defined types from having the same error-reporting benefits.
+Improving the output of these panics has been a goal of proposals since at least mid-2016 (see +non-viable alternatives in the approved RFC for details). It took two more years until RFC 2091 +was approved, much of its rationale for this feature's design having been discovered through the +discussion around several earlier proposals.
+The design in the original RFC limited itself to implementations that could be done inside the +compiler at the time without significant refactoring. However in the year and a half between the +approval of the RFC and the actual implementation work, a revised design was proposed and written +up on the tracking issue. During the course of implementing that, it was also discovered that an +implementation was possible without modifying the number of arguments in a function's MIR, which +would simplify later stages and unlock use in traits.
+Because the RFC's implementation strategy could not readily support traits, the semantics were not +originally specified. They have since been implemented following the path which seemed most correct +to the author and reviewers.
+ +When the compiler sees a reference to an external crate, it needs to load some +information about that crate. This chapter gives an overview of that process, +and the supported file formats for crate libraries.
+A crate dependency can be loaded from an rlib
, dylib
, or rmeta
file. A
+key point of these file formats is that they contain rustc
-specific
+metadata. This metadata allows the compiler to discover enough
+information about the external crate to understand the items it contains,
+which macros it exports, and much more.
An rlib
is an archive file, which is similar to a tar file. This file
+format is specific to rustc
, and may change over time. This file contains:
.o
file for each codegen unit. The
+codegen step can be skipped with the -C linker-plugin-lto
CLI option, which means each .o
+file will only contain LLVM bitcode..o
files. This can
+be used for Link Time Optimization (LTO). This can be removed with the
+-C embed-bitcode=no
CLI option to improve compile times
+and reduce disk space if LTO is not needed.rustc
metadata, in a file named lib.rmeta
.A dylib
is a platform-specific shared library. It includes the rustc
+metadata in a special link section called .rustc
in a compressed format.
An rmeta
file is custom binary format that contains the metadata for the
+crate. This file can be used for fast "checks" of a project by skipping all
+code generation (as is done with cargo check
), collecting enough information
+for documentation (as is done with cargo doc
), or for
+pipelining. This file is created if the
+--emit=metadata
CLI option is used.
rmeta
files do not support linking, since they do not contain compiled
+object files.
The metadata contains a wide swath of different elements. This guide will not
+go into detail of every field it contains. You are encouraged to browse the
+CrateRoot
definition to get a sense of the different elements it contains.
+Everything about metadata encoding and decoding is in the rustc_metadata
+package.
Here are a few highlights of things it contains:
+rustc
compiler. The compiler will refuse to load files
+from any other version.cargo check
skips this for performance reasons.The Strict Version Hash (SVH, also known as the "crate hash") is a 64-bit +hash that is used to ensure that the correct crate dependencies are loaded. It +is possible for a directory to contain multiple copies of the same dependency +built with different settings, or built from different sources. The crate +loader will skip any crates that have the wrong SVH.
+The SVH is also used for the incremental compilation session filename, +though that usage is mostly historic.
+The hash includes a variety of elements:
+-C metadata
via the Stable
+Crate Id, and all CLI options marked with [TRACKED]
).See compute_hir_hash
for where the hash is actually computed.
The StableCrateId
is a 64-bit hash used to identify different crates with
+potentially the same name. It is a hash of the crate name and all the
+-C metadata
CLI options computed in StableCrateId::new
. It is
+used in a variety of places, such as symbol name mangling, crate loading, and
+much more.
By default, all Rust symbols are mangled and incorporate the stable crate id.
+This allows multiple versions of the same crate to be included together. Cargo
+automatically generates -C metadata
hashes based on a variety of factors,
+like the package version, source, and the target kind (a lib and test can have
+the same crate name, so they need to be disambiguated).
Crate loading can have quite a few subtle complexities. During name
+resolution, when an external crate is referenced (via an extern crate
or
+path), the resolver uses the CrateLoader
which is responsible for finding
+the crate libraries and loading the metadata for them. After the dependency
+is loaded, the CrateLoader
will provide the information the resolver needs
+to perform its job (such as expanding macros, resolving paths, etc.).
To load each external crate, the CrateLoader
uses a CrateLocator
to
+actually find the correct files for one specific crate. There is some great
+documentation in the locator
module that goes into detail on how loading
+works, and I strongly suggest reading it to get the full picture.
The location of a dependency can come from several different places. Direct
+dependencies are usually passed with --extern
flags, and the loader can look
+at those directly. Direct dependencies often have references to their own
+dependencies, which need to be loaded, too. These are usually found by
+scanning the directories passed with the -L
flag for any file whose metadata
+contains a matching crate name and SVH. The loader
+will also look at the sysroot to find dependencies.
As crates are loaded, they are kept in the CStore
with the crate metadata
+wrapped in the CrateMetadata
struct. After resolution and expansion, the
+CStore
will make its way into the GlobalCtxt
for the rest of
+compilation.
One trick to improve compile times is to start building a crate as soon as the
+metadata for its dependencies is available. For a library, there is no need to
+wait for the code generation of dependencies to finish. Cargo implements this
+technique by telling rustc
to emit an rmeta
file for each
+dependency as well as an rlib
. As early as it can, rustc
will
+save the rmeta
file to disk before it continues to the code generation
+phase. The compiler sends a JSON message to let the build tool know that it
+can start building the next crate if possible.
The crate loading system is smart enough to know when it
+sees an rmeta
file to use that if the rlib
is not there (or has only been
+partially written).
This pipelining isn't possible for binaries, because the linking phase will +require the code generation of all its dependencies. In the future, it may be +possible to further improve this scenario by splitting linking into a separate +command (see #64191).
+ +Now that we have a list of symbols to generate from the collector, we need to +generate some sort of codegen IR. In this chapter, we will assume LLVM IR, +since that's what rustc usually uses. The actual monomorphization is performed +as we go, while we do the translation.
+Recall that the backend is started by
+rustc_codegen_ssa::base::codegen_crate
. Eventually, this reaches
+rustc_codegen_ssa::mir::codegen_mir
, which does the lowering from
+MIR to LLVM IR.
The code is split into modules which handle particular MIR primitives:
+rustc_codegen_ssa::mir::block
will deal with translating
+blocks and their terminators. The most complicated and also the most
+interesting thing this module does is generating code for function calls,
+including the necessary unwinding handling IR.rustc_codegen_ssa::mir::statement
translates MIR statements.rustc_codegen_ssa::mir::operand
translates MIR operands.rustc_codegen_ssa::mir::place
translates MIR place references.rustc_codegen_ssa::mir::rvalue
translates MIR r-values.Before a function is translated a number of simple and primitive analysis
+passes will run to help us generate simpler and more efficient LLVM IR. An
+example of such an analysis pass would be figuring out which variables are
+SSA-like, so that we can translate them to SSA directly rather than relying on
+LLVM's mem2reg
for those variables. The analysis can be found in
+rustc_codegen_ssa::mir::analyze
.
Usually a single MIR basic block will map to a LLVM basic block, with very few
+exceptions: intrinsic or function calls and less basic MIR statements like
+assert
can result in multiple basic blocks. This is a perfect lede into the
+non-portable LLVM-specific part of the code generation. Intrinsic generation is
+fairly easy to understand as it involves very few abstraction levels in between
+and can be found in rustc_codegen_llvm::intrinsic
.
Everything else will use the builder interface. This is the code that gets
+called in the rustc_codegen_ssa::mir::*
modules discussed above.
++ +TODO: discuss how constants are generated
+
As you probably know, Rust has a very expressive type system that has extensive +support for generic types. But of course, assembly is not generic, so we need +to figure out the concrete types of all the generics before the code can +execute.
+Different languages handle this problem differently. For example, in some +languages, such as Java, we may not know the most precise type of value until +runtime. In the case of Java, this is ok because (almost) all variables are +reference values anyway (i.e. pointers to a heap allocated object). This +flexibility comes at the cost of performance, since all accesses to an object +must dereference a pointer.
+Rust takes a different approach: it monomorphizes all generic types. This
+means that compiler stamps out a different copy of the code of a generic
+function for each concrete type needed. For example, if I use a Vec<u64>
and
+a Vec<String>
in my code, then the generated binary will have two copies of
+the generated code for Vec
: one for Vec<u64>
and another for Vec<String>
.
+The result is fast programs, but it comes at the cost of compile time (creating
+all those copies can take a while) and binary size (all those copies might take
+a lot of space).
Monomorphization is the first step in the backend of the Rust compiler.
+First, we need to figure out what concrete types we need for all the generic +things in our program. This is called collection, and the code that does this +is called the monomorphization collector.
+Take this example:
++fn banana() { + peach::<u64>(); +} + +fn main() { + banana(); +} +
The monomorphization collector will give you a list of [main, banana, peach::<u64>]
. These are the functions that will have machine code generated
+for them. Collector will also add things like statics to that list.
See the collector rustdocs for more info.
+The monomorphization collector is run just before MIR lowering and codegen.
+rustc_codegen_ssa::base::codegen_crate
calls the
+collect_and_partition_mono_items
query, which does monomorphization
+collection and then partitions them into codegen
+units.
For better incremental build times, the CGU partitioner creates two CGU for each source level +modules. One is for "stable" i.e. non-generic code and the other is more volatile code i.e. +monomorphized/specialized instances.
+For dependencies, consider Crate A and Crate B, such that Crate B depends on Crate A. +The following table lists different scenarios for a function in Crate A that might be used by one +or more modules in Crate B.
+Crate A function | Behavior |
---|---|
Non-generic function | Crate A function doesn't appear in any codegen units of Crate B |
Non-generic #[inline] function | Crate A function appears within a single CGU of Crate B, and exists even after post-inlining stage |
Generic function | Regardless of inlining, all monomorphized (specialized) functions from Crate A appear within a single codegen unit for Crate B. The codegen unit exists even after the post inlining stage. |
Generic #[inline] function | - same - |
For more details about the partitioner read the module level documentation.
+As mentioned above, monomorphization produces fast code, but it comes at the +cost of compile time and binary size. MIR optimizations can help a +bit with this.
+In addition to MIR optimizations, rustc attempts to determine when fewer
+copies of functions are necessary and avoid making those copies - known
+as "polymorphization". When a function-like item is found during
+monomorphization collection, the
+rustc_mir_monomorphize::polymorphize::unused_generic_params
+query is invoked, which traverses the MIR of the item to determine on which
+generic parameters the item might not need duplicated.
Currently, polymorphization only looks for unused generic parameters. These +are relatively rare in functions, but closures inherit the generic +parameters of their parent function and it is common for closures to not +use those inherited parameters. Without polymorphization, a copy of these +closures would be created for each copy of the parent function. By +creating fewer copies, less LLVM IR is generated; therefore less needs to be processed.
+unused_generic_params
returns a FiniteBitSet<u64>
where a bit is set if
+the generic parameter of the corresponding index is unused. Any parameters
+after the first sixty-four are considered used.
The results of polymorphization analysis are used in the
+Instance::polymorphize
function to replace the
+Instance
's substitutions for the unused generic parameters with their
+identity substitutions.
Consider the example below:
++fn foo<A, B>() { + let x: Option<B> = None; +} + +fn main() { + foo::<u16, u32>(); + foo::<u64, u32>(); +} +
During monomorphization collection, foo
will be collected with the
+substitutions [u16, u32]
and [u64, u32]
(from its invocations in main
).
+foo
has the identity substitutions [A, B]
(or
+[ty::Param(0), ty::Param(1)]
).
Polymorphization will identify A
as being unused and it will be replaced in
+the substitutions with the identity parameter before being added to the set
+of collected items - thereby reducing the copies from two ([u16, u32]
and
+[u64, u32]
) to one ([A, u32]
).
unused_generic_params
will also be invoked during code generation when the
+symbol name for foo
is being computed for use in the callsites of foo
+(which have the regular substitutions present, otherwise there would be a
+symbol mismatch between the caller and the function).
As a result of polymorphization, items collected during monomorphization +cannot be assumed to be monomorphic.
+It is intended that polymorphization be extended to more advanced cases, +such as where only the size/alignment of a generic parameter are required.
+More details on polymorphization are available in the +master's thesis associated with polymorphization's initial +implementation.
+ +Rust supports building against multiple LLVM versions:
+llvm-main
.By default, Rust uses its own fork in the rust-lang/llvm-project repository.
+This fork is based on a release/$N.x
branch of the upstream project, where
+$N
is either the latest released major version, or the current major version
+in release candidate phase. The fork is never based on the main
development
+branch.
Our LLVM fork only accepts:
+With the exception of one grandfathered-in patch for SGX enablement, we do not +accept functional patches that have not been upstreamed first.
+There are three types of LLVM updates, with different procedures:
+While the current major LLVM version is supported upstream, fixes should be +backported upstream first, and the release branch then merged back into the +Rust fork.
+src/llvm-project
+submodule is always pinned to a branch of the
+rust-lang/llvm-project repository.rustc/a.b-yyyy-mm-dd
).git remote add upstream https://github.com/llvm/llvm-project.git
and
+fetch it using git fetch upstream
.upstream/release/$N.x
branch.src/llvm-project
submodule with
+your bugfix. This can be done locally with git submodule update --remote src/llvm-project
typically.An example PR: +#59089
+Upstream LLVM releases are only supported for two to three months after the +GA release. Once upstream backports are no longer accepted, changes should be +cherry-picked directly to our fork.
+src/llvm-project
+submodule is always pinned to a branch of the
+rust-lang/llvm-project repository.rustc/a.b-yyyy-mm-dd
).git remote add upstream https://github.com/llvm/llvm-project.git
and
+fetch it using git fetch upstream
.git cherry-pick -x
.src/llvm-project
submodule with
+your bugfix. This can be done locally with git submodule update --remote src/llvm-project
typically.An example PR: +#59089
+Unlike bugfixes, +updating to a new release of LLVM typically requires a lot more work. +This is where we can't reasonably cherry-pick commits backwards, +so we need to do a full update. +There's a lot of stuff to do here, +so let's go through each in detail.
+LLVM announces that its latest release version has branched.
+This will show up as a branch in the llvm/llvm-project repository,
+typically named release/$N.x
,
+where $N
is the version of LLVM that's being released.
Create a new branch in the rust-lang/llvm-project repository
+from this release/$N.x
branch,
+and name it rustc/a.b-yyyy-mm-dd
,
+where a.b
is the current version number of LLVM in-tree
+at the time of the branch,
+and the remaining part is the current date.
Apply Rust-specific patches to the llvm-project repository. +All features and bugfixes are upstream, +but there's often some weird build-related patches +that don't make sense to upstream. +These patches are typically the latest patches in the +rust-lang/llvm-project branch that rustc is currently using.
+Build the new LLVM in the rust
repository.
+To do this,
+you'll want to update the src/llvm-project
repository to your branch,
+and the revision you've created.
+It's also typically a good idea to update .gitmodules
with the new
+branch name of the LLVM submodule.
+Make sure you've committed changes to
+src/llvm-project
to ensure submodule updates aren't reverted.
+Some commands you should execute are:
./x build src/llvm-project
- test that LLVM still builds./x build
- build the rest of rustcYou'll likely need to update llvm-wrapper/*.cpp
+to compile with updated LLVM bindings.
+Note that you should use #ifdef
and such to ensure
+that the bindings still compile on older LLVM versions.
Note that profile = "compiler"
and other defaults set by ./x setup
+download LLVM from CI instead of building it from source.
+You should disable this temporarily to make sure your changes are being used.
+This is done by having the following setting in config.toml
:
[llvm]
+download-ci-llvm = false
+
+Test for regressions across other platforms. LLVM often has at least one bug +for non-tier-1 architectures, so it's good to do some more testing before +sending this to bors! If you're low on resources you can send the PR as-is +now to bors, though, and it'll get tested anyway.
+Ideally, build LLVM and test it on a few platforms:
+Afterwards, run some docker containers that CI also does:
+./src/ci/docker/run.sh wasm32
./src/ci/docker/run.sh arm-android
./src/ci/docker/run.sh dist-various-1
./src/ci/docker/run.sh dist-various-2
./src/ci/docker/run.sh armhf-gnu
Prepare a PR to rust-lang/rust
. Work with maintainers of
+rust-lang/llvm-project
to get your commit in a branch of that repository,
+and then you can send a PR to rust-lang/rust
. You'll change at least
+src/llvm-project
and will likely also change llvm-wrapper
as well.
++For prior art, here are some previous LLVM updates:
+ +
Note that sometimes it's easiest to land llvm-wrapper
compatibility as a PR
+before actually updating src/llvm-project
.
+This way,
+while you're working through LLVM issues,
+others interested in trying out the new LLVM can benefit from work you've done
+to update the C++ bindings.
Over the next few months,
+LLVM will continually push commits to its release/a.b
branch.
+We will often want to have those bug fixes as well.
+The merge process for that is to use git merge
itself to merge LLVM's
+release/a.b
branch with the branch created in step 2.
+This is typically
+done multiple times when necessary while LLVM's release branch is baking.
LLVM then announces the release of version a.b
.
After LLVM's official release, +we follow the process of creating a new branch on the +rust-lang/llvm-project repository again, +this time with a new date. +It is only then that the PR to update Rust to use that version is merged.
+The commit history of rust-lang/llvm-project
+should look much cleaner as a git rebase
is done,
+where just a few Rust-specific commits are stacked on top of stock LLVM's release branch.
Ideally the above instructions are pretty smooth, but here's some caveats to +keep in mind while going through them:
+The borrow check is Rust's "secret sauce" – it is tasked with +enforcing a number of properties:
+The borrow checker operates on the MIR. An older implementation operated on the +HIR. Doing borrow checking on MIR has several advantages:
+The borrow checker source is found in
+the rustc_borrowck
crate. The main entry point is
+the mir_borrowck
query.
replace_regions_in_mir
to modify our local MIR.
+Among other things, this function will replace all of the regions
+in the MIR with fresh inference variables.*a + 1
, then we would check that the variable a
is initialized
+and that it is not mutably borrowed, as either of those would
+require an error to be reported. Doing this check requires the results of all
+the previous analyses.We generally require the type of locals to be well-formed whenever the +local is used. This includes proving the where-bounds of the local and +also requires all regions used by it to be live.
+The only exception to this is when implicitly dropping values when they +go out of scope. This does not necessarily require the value to be live:
++fn main() { + let x = vec![]; + { + let y = String::from("I am temporary"); + x.push(&y); + } + // `x` goes out of scope here, after the reference to `y` + // is invalidated. This means that while dropping `x` its type + // is not well-formed as it contain regions which are not live. +} +
This is only sound if dropping the value does not try to access any dead
+region. We check this by requiring the type of the value to be
+drop-live.
+The requirements for which are computed in fn dropck_outlives
.
The rest of this section uses the following type definition for a type +which requires its region parameter to be live:
++#![allow(unused)] +fn main() { +struct PrintOnDrop<'a>(&'a str); +impl<'a> Drop for PrintOnDrop<'_> { + fn drop(&mut self) { + println!("{}", self.0); + } +} +} +
At its core, a value of type T
is dropped by executing its "drop
+glue". Drop glue is compiler generated and first calls <T as Drop>::drop
and then recursively calls the drop glue of any recursively
+owned values.
T
has an explicit Drop
impl, call <T as Drop>::drop
.T
implements Drop
, recurse into all values
+owned by T
:
+ManuallyDrop<U>
which is not considered to own U
.
+PhantomData<U>
also does not own anything.
+closures and generators own their captured upvars.Whether a type has drop glue is returned by fn Ty::needs_drop
.
For types which do not implement Drop
themselves, we can also
+partially move parts of the value before dropping the rest. In this case
+only the drop glue for the not-yet moved values is called, e.g.
+fn main() { + let mut x = (PrintOnDrop("third"), PrintOnDrop("first")); + drop(x.1); + println!("second") +} +
During MIR building we assume that a local may get dropped whenever it
+goes out of scope as long as its type needs drop. Computing the exact
+drop glue for a variable happens after borrowck in the
+ElaborateDrops
pass. This means that even if some part of the local
+have been dropped previously, dropck still requires this value to be
+live. This is the case even if we completely moved a local.
+fn main() { + let mut x; + { + let temp = String::from("I am temporary"); + x = PrintOnDrop(&temp); + drop(x); + } +} //~ ERROR `temp` does not live long enough. +
It should be possible to add some amount of drop elaboration before +borrowck, allowing this example to compile. There is an unstable feature +to move drop elaboration before const checking: +#73255. Such a feature +gate does not exist for doing some drop elaboration before borrowck, +although there's a relevant +MCP.
+you can consider trait objects to have a builtin Drop
+implementation which directly uses the drop_in_place
provided by the
+vtable. This Drop
implementation requires all its generic parameters
+to be live.
dropck_outlives
There are two distinct "liveness" computations that we perform:
+v
is use-live at location L
if it may be "used" later; a
+use here is basically anything that is not a dropv
is drop-live at location L
if it maybe dropped laterWhen things are use-live, their entire type must be valid at L
. When
+they are drop-live, all regions that are required by dropck must be
+valid at L
. The values dropped in the MIR are places.
The constraints computed by dropck_outlives
for a type closely match
+the generated drop glue for that type. Unlike drop glue,
+dropck_outlives
cares about the types of owned values, not the values
+itself. For a value of type T
T
has an explicit Drop
, require all generic arguments to be
+live, unless they are marked with #[may_dangle]
in which case they
+are fully ignoredT
has an explicit Drop
, recurse into all
+types owned by T
+ManuallyDrop<U>
which is not considered to own
+U
. We consider PhantomData<U>
to own U
.The sections marked in bold are cases where dropck_outlives
considers
+types to be owned which are ignored by Ty::needs_drop
. We only rely on
+dropck_outlives
if Ty::needs_drop
for the containing local returned
+true
.This means liveness requirements can change depending on whether
+a type is contained in a larger local. This is inconsistent, and
+should be fixed: an example for
+arrays
+and for
+PhantomData
.2
One possible way these inconsistencies can be fixed is by MIR building
+to be more pessimistic, probably by making Ty::needs_drop
weaker, or
+alternatively, changing dropck_outlives
to be more precise, requiring
+fewer regions to be live.
Part of the borrow checker's job is to track which variables are +"initialized" at any given point in time -- this also requires +figuring out where moves occur and tracking those.
+From a user's perspective, initialization -- giving a variable some +value -- and moves -- transferring ownership to another place -- might +seem like distinct topics. Indeed, our borrow checker error messages +often talk about them differently. But within the borrow checker, +they are not nearly as separate. Roughly speaking, the borrow checker +tracks the set of "initialized places" at any point in the source +code. Assigning to a previously uninitialized local variable adds it +to that set; moving from a local variable removes it from that set.
+Consider this example:
+fn foo() {
+ let a: Vec<u32>;
+
+ // a is not initialized yet
+
+ a = vec![22];
+
+ // a is initialized here
+
+ std::mem::drop(a); // a is moved here
+
+ // a is no longer initialized here
+
+ let l = a.len(); //~ ERROR
+}
+
+Here you can see that a
starts off as uninitialized; once it is
+assigned, it becomes initialized. But when drop(a)
is called, that
+moves a
into the call, and hence it becomes uninitialized again.
To make it easier to peruse, this section is broken into a number of +subsections:
+In reality, it's not enough to track initialization at the granularity +of local variables. Rust also allows us to do moves and initialization +at the field granularity:
+fn foo() {
+ let a: (Vec<u32>, Vec<u32>) = (vec![22], vec![44]);
+
+ // a.0 and a.1 are both initialized
+
+ let b = a.0; // moves a.0
+
+ // a.0 is not initialized, but a.1 still is
+
+ let c = a.0; // ERROR
+ let d = a.1; // OK
+}
+
+To handle this, we track initialization at the granularity of a move
+path. A MovePath
represents some location that the user can
+initialize, move, etc. So e.g. there is a move-path representing the
+local variable a
, and there is a move-path representing a.0
. Move
+paths roughly correspond to the concept of a Place
from MIR, but
+they are indexed in ways that enable us to do move analysis more
+efficiently.
Although there is a MovePath
data structure, they are never referenced
+directly. Instead, all the code passes around indices of type
+MovePathIndex
. If you need to get information about a move path, you use
+this index with the move_paths
field of the MoveData
. For
+example, to convert a MovePathIndex
mpi
into a MIR Place
, you might
+access the MovePath::place
field like so:
move_data.move_paths[mpi].place
+
+One of the first things we do in the MIR borrow check is to construct
+the set of move paths. This is done as part of the
+MoveData::gather_moves
function. This function uses a MIR visitor
+called MoveDataBuilder
to walk the MIR and look at how each Place
+within is accessed. For each such Place
, it constructs a
+corresponding MovePathIndex
. It also records when/where that
+particular move path is moved/initialized, but we'll get to that in a
+later section.
We don't actually create a move-path for every Place
that gets
+used. In particular, if it is illegal to move from a Place
, then
+there is no need for a MovePathIndex
. Some examples:
MovePathIndex
+for static variables.foo: [String; 3]
,
+there would be no move-path for foo[1]
.foo: &String
,
+there would be no move-path for *foo
.These rules are enforced by the move_path_for
function, which
+converts a Place
into a MovePathIndex
-- in error cases like
+those just discussed, the function returns an Err
. This in turn
+means we don't have to bother tracking whether those places are
+initialized (which lowers overhead).
If you have a Place
and you would like to convert it to a MovePathIndex
, you
+can do that using the MovePathLookup
structure found in the rev_lookup
field
+of MoveData
. There are two different methods:
find_local
, which takes a mir::Local
representing a local
+variable. This is the easier method, because we always create a
+MovePathIndex
for every local variable.find
, which takes an arbitrary Place
. This method is a bit
+more annoying to use, precisely because we don't have a
+MovePathIndex
for every Place
(as we just discussed in
+the "illegal move paths" section). Therefore, find
returns a
+LookupResult
indicating the closest path it was able to find
+that exists (e.g., for foo[1]
, it might return just the path for
+foo
).As we noted above, move-paths are stored in a big vector and
+referenced via their MovePathIndex
. However, within this vector,
+they are also structured into a tree. So for example if you have the
+MovePathIndex
for a.b.c
, you can go to its parent move-path
+a.b
. You can also iterate over all children paths: so, from a.b
,
+you might iterate to find the path a.b.c
(here you are iterating
+just over the paths that are actually referenced in the source,
+not all possible paths that could have been referenced). These
+references are used for example in the
+find_in_move_path_or_its_descendants
function, which determines
+whether a move-path (e.g., a.b
) or any child of that move-path
+(e.g.,a.b.c
) matches a given predicate.
In this chapter we discuss the various restrictions we impose on the generic arguments of
+opaque types when defining their hidden types
+Opaque<'a, 'b, .., A, B, ..> := SomeHiddenType
.
These restrictions are implemented in borrow checking (Source) +as it is the final step opaque types inference.
+For type arguments, two restrictions are necessary: each type argument must be +(1) a type parameter and +(2) is unique among the generic arguments. +The same is applied to const arguments.
+Example of case (1):
++#![allow(unused)] +fn main() { +type Opaque<X> = impl Sized; + +// `T` is a type parameter. +// Opaque<T> := (); +fn good<T>() -> Opaque<T> {} + +// `()` is not a type parameter. +// Opaque<()> := (); +fn bad() -> Opaque<()> {} //~ ERROR +} +
Example of case (2):
++#![allow(unused)] +fn main() { +type Opaque<X, Y> = impl Sized; + +// `T` and `U` are unique in the generic args. +// Opaque<T, U> := T; +fn good<T, U>(t: T, _u: U) -> Opaque<T, U> { t } + +// `T` appears twice in the generic args. +// Opaque<T, T> := T; +fn bad<T>(t: T) -> Opaque<T, T> { t } //~ ERROR +} +
Motivation: In the first case Opaque<()> := ()
, the hidden type is ambiguous because
+it is compatible with two different interpretaions: Opaque<X> := X
and Opaque<X> := ()
.
+Similarly for the second case Opaque<T, T> := T
, it is ambiguous whether it should be
+interpreted as Opaque<X, Y> := X
or as Opaque<X, Y> := Y
.
+Because of this ambiguity, both cases are rejected as invalid defining uses.
Each lifetime argument must be unique in the arguments list and must not be 'static
.
+This is in order to avoid an ambiguity with hidden type inference similar to the case of
+type parameters.
+For example, the invalid defining use below Opaque<'static> := Inv<'static>
is compatible with
+both Opaque<'x> := Inv<'static>
and Opaque<'x> := Inv<'x>
.
+#![allow(unused)] +fn main() { +type Opaque<'x> = impl Sized + 'x; +type Inv<'a> = Option<*mut &'a ()>; + +fn good<'a>() -> Opaque<'a> { Inv::<'static>::None } + +fn bad() -> Opaque<'static> { Inv::<'static>::None } +//~^ ERROR +} +
+#![allow(unused)] +fn main() { +type Opaque<'x, 'y> = impl Trait<'x, 'y>; + +fn good<'a, 'b>() -> Opaque<'a, 'b> {} + +fn bad<'a>() -> Opaque<'a, 'a> {} +//~^ ERROR +} +
Semantic lifetime equality: +One complexity with lifetimes compared to type parameters is that +two lifetimes that are syntactically different may be semantically equal. +Therefore, we need to be cautious when verifying that the lifetimes are unique.
++#![allow(unused)] +fn main() { +// This is also invalid because `'a` is *semantically* equal to `'static`. +fn still_bad_1<'a: 'static>() -> Opaque<'a> {} +//~^ Should error! + +// This is also invalid because `'a` and `'b` are *semantically* equal. +fn still_bad_2<'a: 'b, 'b: 'a>() -> Opaque<'a, 'b> {} +//~^ Should error! +} +
An exception to the uniqueness rule above is when the bounds at the opaque type's definition require
+a lifetime parameter to be equal to another one or to the 'static
lifetime.
+#![allow(unused)] +fn main() { +// The definition requires `'x` to be equal to `'static`. +type Opaque<'x: 'static> = impl Sized + 'x; + +fn good() -> Opaque<'static> {} +} +
Motivation: an attempt to implement the uniqueness restriction for RPITs resulted in a +breakage found by crater. +This can be mitigated by this exception to the rule. +An example of the code that would otherwise break:
++#![allow(unused)] +fn main() { +struct Type<'a>(&'a ()); +impl<'a> Type<'a> { + // `'b == 'a` + fn do_stuff<'b: 'a>(&'b self) -> impl Trait<'a, 'b> {} +} +} +
Why this is correct: for such a defining use like Opaque<'a, 'a> := &'a str
,
+it can be interpreted in either way—either as Opaque<'x, 'y> := &'x str
or as
+Opaque<'x, 'y> := &'y str
and it wouldn't matter because every use of Opaque
+will guarantee that both parameters are equal as per the well-formedness rules.
Only universally quantified lifetimes are allowed in the opaque type arguments. +This includes lifetime parameters and placeholders.
++#![allow(unused)] +fn main() { +type Opaque<'x> = impl Sized + 'x; + +fn test<'a>() -> Opaque<'a> { + // `Opaque<'empty> := ()` + let _: Opaque<'_> = (); + //~^ ERROR +} +} +
Motivation:
+This makes the lifetime and type arguments behave consistently but this is only as a bonus.
+The real reason behind this restriction is purely technical, as the member constraints algorithm
+faces a fundamental limitation:
+When encountering an opaque type definition Opaque<'?1> := &'?2 u8
,
+a member constraint '?2 member-of ['static, '?1]
is registered.
+In order for the algorithm to pick the right choice, the complete set of "outlives" relationships
+between the choice regions ['static, '?1]
must already be known before doing the region
+inference. This can be satisfied only if each choice region is either:
RegionKind::Re{EarlyParam,LateParam,Placeholder,Static}
,
+because the relations between universal regions are completely known, prior to region inference,
+from the explicit and implied bounds.Strict lifetime equality: +We say that two lifetimes are strictly equal if there are bidirectional outlives constraints +between them. In NLL terms, this means the lifetimes are part of the same SCC. +Importantly this type of equality can be evaluated prior to full region inference +(but of course after constraint collection). +The other type of equality is when region inference ends up giving two lifetimes variables +the same value even if they are not strictly equal. +See #113971 for how we used to conflate the difference.
+interaction with "once modulo regions" restriction
+In the example above, note the opaque type in the signature is Opaque<'a>
and the one in the
+invalid defining use is Opaque<'empty>
.
+In the proposed MiniTAIT plan, namely the "once modulo regions" rule,
+we already disallow this.
+Although it might appear that "universal lifetimes" restriction becomes redundant as it logically
+follows from "MiniTAIT" restrictions, the subsequent related discussion on lifetime equality and
+closures remains relevant.
When the opaque type is defined in a closure/coroutine/inline-const body, universal lifetimes that
+are "external" to the closure are not allowed in the opaque type arguments.
+External regions are defined in RegionClassification::External
Example: (This one happens to compile in the current nightly but more practical examples are +already rejected with confusing errors.)
++#![allow(unused)] +fn main() { +type Opaque<'x> = impl Sized + 'x; + +fn test<'a>() -> Opaque<'a> { + let _ = || { + // `'a` is external to the closure + let _: Opaque<'a> = (); + //~^ Should be an error! + }; + () +} +} +
Motivation: +In closure bodies, external lifetimes, although being categorized as "universal" lifetimes, +behave more like existential lifetimes in that the relations between them are not known ahead of +time, instead their values are inferred just like existential lifetimes and the requirements are +propagated back to the parent fn. This breaks the member constraints algorithm as described above:
+++In order for the algorithm to pick the right choice, the complete set of “outlives” relationships +between the choice regions
+['static, '?1]
must already be known before doing the region inference
Here is an example that details how :
++#![allow(unused)] +fn main() { +type Opaque<'x, 'y> = impl Sized; + +// +fn test<'a, 'b>(s: &'a str) -> impl FnOnce() -> Opaque<'a, 'b> { + move || { s } + //~^ ERROR hidden type for `Opaque<'_, '_>` captures lifetime that does not appear in bounds +} + +// The above closure body is desugared into something like: +fn test::{closure#0}(_upvar: &'?8 str) -> Opaque<'?6, '?7> { + return _upvar +} + +// where `['?8, '?6, ?7]` are universal lifetimes *external* to the closure. +// There are no known relations between them *inside* the closure. +// But in the parent fn it is known that `'?6: '?8`. +// +// When encountering an opaque definition `Opaque<'?6, '?7> := &'8 str`, +// The member constraints algorithm does not know enough to safely make `?8 = '?6`. +// For this reason, it errors with a sensible message: +// "hidden type captures lifetime that does not appear in bounds". +} +
Without this restrictions error messages are consfusing and, more impotantly, there is a risk that +we accept code the we would likely break in the future because member constraints are super broken +in closures.
+Output types:
+I believe the most common scenario where this causes issues in real-world code is with
+closure/async-block output types. It is worth noting that there is a discrepancy between closures
+and async blocks that further demonstrates this issue and is attributed to the
+hack of replace_opaque_types_with_inference_vars
,
+which is applied to futures only.
+#![allow(unused)] +fn main() { +type Opaque<'x> = impl Sized + 'x; +fn test<'a>() -> impl FnOnce() -> Opaque<'a> { + // Output type of the closure is Opaque<'a> + // -> hidden type definition happens *inside* the closure + // -> rejected. + move || {} + //~^ ERROR expected generic lifetime parameter, found `'_` +} +} +
+ +#![allow(unused)] +fn main() { +use std::future::Future; +type Opaque<'x> = impl Sized + 'x; +fn test<'a>() -> impl Future<Output = Opaque<'a>> { + // Output type of the async block is unit `()` + // -> hidden type definition happens in the parent fn + // -> accepted. + async move {} +} +} +
The MIR-based region checking code is located in the rustc_mir::borrow_check
+module.
The MIR-based region analysis consists of two major functions:
+replace_regions_in_mir
, invoked first, has two jobs:
+'a
in fn foo<'a>(&'a u32) { ... }
). These are called the "universal" or "free" regions – in
+particular, they are the regions that appear free in the
+function body.compute_regions
, invoked second: this is given as argument the
+results of move analysis. It has the job of computing values for all
+the inference variables that replace_regions_in_mir
introduced.
+RegionInferenceContext
and invoking its solve
+method.The UniversalRegions
type represents a collection of universal regions
+corresponding to some MIR DefId
. It is constructed in
+replace_regions_in_mir
when we replace all regions with fresh inference
+variables. UniversalRegions
contains indices for all the free regions in
+the given MIR along with any relationships that are known to hold between
+them (e.g. implied bounds, where clauses, etc.).
For example, given the MIR for the following function:
++#![allow(unused)] +fn main() { +fn foo<'a>(x: &'a u32) { + // ... +} +} +
we would create a universal region for 'a
and one for 'static
. There may
+also be some complications for handling closures, but we will ignore those for
+the moment.
TODO: write about how these regions are computed.
+ +The value of a region can be thought of as a set. This set contains all
+points in the MIR where the region is valid along with any regions that are
+outlived by this region (e.g. if 'a: 'b
, then end('b)
is in the set for
+'a
); we call the domain of this set a RegionElement
. In the code, the value
+for all regions is maintained in the rustc_borrowck::region_infer
module.
+For each region we maintain a set storing what elements are present in its value (to make this
+efficient, we give each kind of element an index, the RegionElementIndex
, and
+use sparse bitsets).
The kinds of region elements are as follows:
+location
in the MIR control-flow graph: a location is just
+the pair of a basic block and an index. This identifies the point
+on entry to the statement with that index (or the terminator, if
+the index is equal to statements.len()
).end('a)
for each universal region 'a
,
+corresponding to some portion of the caller's (or caller's caller,
+etc) control-flow graph.end('static)
corresponding
+to the remainder of program execution after this function returns.!1
for each placeholder region !1
. This
+corresponds (intuitively) to some unknown set of other elements –
+for details on placeholders, see the section
+placeholders and universes.Before we can infer the value of regions, we need to collect +constraints on the regions. The full set of constraints is described +in the section on constraint propagation, but the two most +common sorts of constraints are:
+'a: 'b
). Outlives constraints are generated by the MIR type
+checker.So how do we compute the contents of a region? This process is called region +inference. The high-level idea is pretty simple, but there are some details we +need to take care of.
+Here is the high-level idea: we start off each region with the MIR locations we
+know must be in it from the liveness constraints. From there, we use all of the
+outlives constraints computed from the type checker to propagate the
+constraints: for each region 'a
, if 'a: 'b
, then we add all elements of
+'b
to 'a
, including end('b)
. This all happens in
+propagate_constraints
.
Then, we will check for errors. We first check that type tests are satisfied by
+calling check_type_tests
. This checks constraints like T: 'a
. Second, we
+check that universal regions are not "too big". This is done by calling
+check_universal_regions
. This checks that for each region 'a
if 'a
+contains the element end('b)
, then we must already know that 'a: 'b
holds
+(e.g. from a where clause). If we don't already know this, that is an error...
+well, almost. There is some special handling for closures that we will discuss
+later.
Consider the following example:
+fn foo<'a, 'b>(x: &'a usize) -> &'b usize {
+ x
+}
+
+Clearly, this should not compile because we don't know if 'a
outlives 'b
+(if it doesn't then the return value could be a dangling reference).
Let's back up a bit. We need to introduce some free inference variables (as is
+done in replace_regions_in_mir
). This example doesn't use the exact regions
+produced, but it (hopefully) is enough to get the idea across.
fn foo<'a, 'b>(x: &'a /* '#1 */ usize) -> &'b /* '#3 */ usize {
+ x // '#2, location L1
+}
+
+Some notation: '#1
, '#3
, and '#2
represent the universal regions for the
+argument, return value, and the expression x
, respectively. Additionally, I
+will call the location of the expression x
L1
.
So now we can use the liveness constraints to get the following starting points:
+Region | Contents |
---|---|
'#1 | |
'#2 | L1 |
'#3 | L1 |
Now we use the outlives constraints to expand each region. Specifically, we
+know that '#2: '#3
...
Region | Contents |
---|---|
'#1 | L1 |
'#2 | L1, end('#3) // add contents of '#3 and end('#3) |
'#3 | L1 |
... and '#1: '#2
, so ...
Region | Contents |
---|---|
'#1 | L1, end('#2), end('#3) // add contents of '#2 and end('#2) |
'#2 | L1, end('#3) |
'#3 | L1 |
Now, we need to check that no regions were too big (we don't have any type
+tests to check in this case). Notice that '#1
now contains end('#3)
, but
+we have no where
clause or implied bound to say that 'a: 'b
... that's an
+error!
The RegionInferenceContext
type contains all of the information needed to
+do inference, including the universal regions from replace_regions_in_mir
and
+the constraints computed for each region. It is constructed just after we
+compute the liveness constraints.
Here are some of the fields of the struct:
+constraints
: contains all the outlives constraints.liveness_constraints
: contains all the liveness constraints.universal_regions
: contains the UniversalRegions
returned by
+replace_regions_in_mir
.universal_region_relations
: contains relations known to be true about
+universal regions. For example, if we have a where clause that 'a: 'b
, that
+relation is assumed to be true while borrow checking the implementation (it
+is checked at the caller), so universal_region_relations
would contain 'a: 'b
.type_tests
: contains some constraints on types that we must check after
+inference (e.g. T: 'a
).closure_bounds_mapping
: used for propagating region constraints from
+closures back out to the creator of the closure.TODO: should we discuss any of the others fields? What about the SCCs?
+Ok, now that we have constructed a RegionInferenceContext
, we can do
+inference. This is done by calling the solve
method on the context. This
+is where we call propagate_constraints
and then check the resulting type
+tests and universal regions, as discussed above.
When we are checking the type tests and universal regions, we may come +across a constraint that we can't prove yet if we are in a closure +body! However, the necessary constraints may actually hold (we just +don't know it yet). Thus, if we are inside a closure, we just collect +all the constraints we can't prove yet and return them. Later, when we +are borrow check the MIR node that created the closure, we can also +check that these constraints hold. At that time, if we can't prove +they hold, we report an error.
+ +The main work of the region inference is constraint propagation,
+which is done in the propagate_constraints
function. There are
+three sorts of constraints that are used in NLL, and we'll explain how
+propagate_constraints
works by "layering" those sorts of constraints
+on one at a time (each of them is fairly independent from the others):
R live at E
), which arise from liveness;R1: R2
), which arise from subtyping;member R_m of [R_c...]
), which arise from impl Trait.In this chapter, we'll explain the "heart" of constraint propagation, +covering both liveness and outlives constraints.
+Conceptually, region inference is a "fixed-point" computation. It is
+given some set of constraints {C}
and it computes a set of values
+Values: R -> {E}
that maps each region R
to a set of elements
+{E}
(see here for more notes on region elements):
Values(R) = {}
for all regions R
.Values
as needed to satisfy the constraintAs a simple example, if we have a liveness constraint R live at E
,
+then we can apply Values(R) = Values(R) union {E}
to make the
+constraint be satisfied. Similarly, if we have an outlives constraints
+R1: R2
, we can apply Values(R1) = Values(R1) union Values(R2)
.
+(Member constraints are more complex and we discuss them in this section.)
In practice, however, we are a bit more clever. Instead of applying +the constraints in a loop, we can analyze the constraints and figure +out the correct order to apply them, so that we only have to apply +each constraint once in order to find the final result.
+Similarly, in the implementation, the Values
set is stored in the
+scc_values
field, but they are indexed not by a region but by a
+strongly connected component (SCC). SCCs are an optimization that
+avoids a lot of redundant storage and computation. They are explained
+in the section on outlives constraints.
A liveness constraint arises when some variable whose type +includes a region R is live at some point P. This simply means that +the value of R must include the point P. Liveness constraints are +computed by the MIR type checker.
+A liveness constraint R live at E
is satisfied if E
is a member of
+Values(R)
. So to "apply" such a constraint to Values
, we just have
+to compute Values(R) = Values(R) union {E}
.
The liveness values are computed in the type-check and passed to the
+region inference upon creation in the liveness_constraints
argument.
+These are not represented as individual constraints like R live at E
+though; instead, we store a (sparse) bitset per region variable (of
+type LivenessValues
). This way we only need a single bit for each
+liveness constraint.
One thing that is worth mentioning: All lifetime parameters are always +considered to be live over the entire function body. This is because +they correspond to some portion of the caller's execution, and that +execution clearly includes the time spent in this function, since the +caller is waiting for us to return.
+An outlives constraint 'a: 'b
indicates that the value of 'a
must
+be a superset of the value of 'b
. That is, an outlives
+constraint R1: R2
is satisfied if Values(R1)
is a superset of
+Values(R2)
. So to "apply" such a constraint to Values
, we just
+have to compute Values(R1) = Values(R1) union Values(R2)
.
One observation that follows from this is that if you have R1: R2
+and R2: R1
, then R1 = R2
must be true. Similarly, if you have:
R1: R2
+R2: R3
+R3: R4
+R4: R1
+
+then R1 = R2 = R3 = R4
follows. We take advantage of this to make things
+much faster, as described shortly.
In the code, the set of outlives constraints is given to the region
+inference context on creation in a parameter of type
+OutlivesConstraintSet
. The constraint set is basically just a list of 'a: 'b
constraints.
In order to work more efficiently with outlives constraints, they are
+converted into the form of a graph, where the nodes of the
+graph are region variables ('a
, 'b
) and each constraint 'a: 'b
+induces an edge 'a -> 'b
. This conversion happens in the
+RegionInferenceContext::new
function that creates the inference
+context.
When using a graph representation, we can detect regions that must be equal +by looking for cycles. That is, if you have a constraint like
+'a: 'b
+'b: 'c
+'c: 'd
+'d: 'a
+
+then this will correspond to a cycle in the graph containing the
+elements 'a...'d
.
Therefore, one of the first things that we do in propagating region
+values is to compute the strongly connected components (SCCs) in
+the constraint graph. The result is stored in the constraint_sccs
+field. You can then easily find the SCC that a region r
is a part of
+by invoking constraint_sccs.scc(r)
.
Working in terms of SCCs allows us to be more efficient: if we have a
+set of regions 'a...'d
that are part of a single SCC, we don't have
+to compute/store their values separately. We can just store one value
+for the SCC, since they must all be equal.
If you look over the region inference code, you will see that a number
+of fields are defined in terms of SCCs. For example, the
+scc_values
field stores the values of each SCC. To get the value
+of a specific region 'a
then, we first figure out the SCC that the
+region is a part of, and then find the value of that SCC.
When we compute SCCs, we not only figure out which regions are a +member of each SCC, we also figure out the edges between them. So for example +consider this set of outlives constraints:
+'a: 'b
+'b: 'a
+
+'a: 'c
+
+'c: 'd
+'d: 'c
+
+Here we have two SCCs: S0 contains 'a
and 'b
, and S1 contains 'c
+and 'd
. But these SCCs are not independent: because 'a: 'c
, that
+means that S0: S1
as well. That is -- the value of S0
must be a
+superset of the value of S1
. One crucial thing is that this graph of
+SCCs is always a DAG -- that is, it never has cycles. This is because
+all the cycles have been removed to form the SCCs themselves.
The liveness constraints that come in from the type-checker are
+expressed in terms of regions -- that is, we have a map like
+Liveness: R -> {E}
. But we want our final result to be expressed
+in terms of SCCs -- we can integrate these liveness constraints very
+easily just by taking the union:
for each region R:
+ let S be the SCC that contains R
+ Values(S) = Values(S) union Liveness(R)
+
+In the region inferencer, this step is done in RegionInferenceContext::new
.
Once we have computed the DAG of SCCs, we use that to structure out
+entire computation. If we have an edge S1 -> S2
between two SCCs,
+that means that Values(S1) >= Values(S2)
must hold. So, to compute
+the value of S1
, we first compute the values of each successor S2
.
+Then we simply union all of those values together. To use a
+quasi-iterator-like notation:
Values(S1) =
+ s1.successors()
+ .map(|s2| Values(s2))
+ .union()
+
+In the code, this work starts in the propagate_constraints
+function, which iterates over all the SCCs. For each SCC S1
, we
+compute its value by first computing the value of its
+successors. Since SCCs form a DAG, we don't have to be concerned about
+cycles, though we do need to keep a set around to track whether we
+have already processed a given SCC or not. For each successor S2
, once
+we have computed S2
's value, we can union those elements into the
+value for S1
. (Although we have to be careful in this process to
+properly handle higher-ranked
+placeholders. Note that the value
+for S1
already contains the liveness constraints, since they were
+added in RegionInferenceContext::new
.
Once that process is done, we now have the "minimal value" for S1
,
+taking into account all of the liveness and outlives
+constraints. However, in order to complete the process, we must also
+consider member constraints, which are described in a later
+section.
TODO: we should discuss how to generate errors from the results of these analyses.
+ +"Universal regions" is the name that the code uses to refer to "named
+lifetimes" -- e.g., lifetime parameters and 'static
. The name
+derives from the fact that such lifetimes are "universally quantified"
+(i.e., we must make sure the code is true for all values of those
+lifetimes). It is worth spending a bit of discussing how lifetime
+parameters are handled during region inference. Consider this example:
fn foo<'a, 'b>(x: &'a u32, y: &'b u32) -> &'b u32 {
+ x
+}
+
+This example is intended not to compile, because we are returning x
,
+which has type &'a u32
, but our signature promises that we will
+return a &'b u32
value. But how are lifetimes like 'a
and 'b
+integrated into region inference, and how this error wind up being
+detected?
Early on in region inference, one of the first things we do is to
+construct a UniversalRegions
struct. This struct tracks the
+various universal regions in scope on a particular function. We also
+create a UniversalRegionRelations
struct, which tracks their
+relationships to one another. So if you have e.g. where 'a: 'b
, then
+the UniversalRegionRelations
struct would track that 'a: 'b
is
+known to hold (which could be tested with the outlives
function.
One important aspect of how NLL region inference works is that all
+lifetimes are represented as numbered variables. This means that the
+only variant of region_kind::RegionKind
that we use is the ReVar
+variant. These region variables are broken into two major categories,
+based on their index:
In fact, the universal regions can be further subdivided based on
+where they were brought into scope (see the RegionClassification
+type). These subdivisions are not important for the topics discussed
+here, but become important when we consider closure constraint
+propagation, so we discuss them there.
As noted previously, the value that we infer for each region is a set
+{E}
. The elements of this set can be points in the control-flow
+graph, but they can also be an element end('a)
corresponding to each
+universal lifetime 'a
. If the value for some region R0
includes
+end('a
), then this implies that R0
must extend until the end of 'a
+in the caller.
During region inference, we compute a value for each universal region +in the same way as we compute values for other regions. This value +represents, effectively, the lower bound on that universal region +-- the things that it must outlive. We now describe how we use this +value to check for errors.
+All universal regions have an initial liveness constraint that
+includes the entire function body. This is because lifetime parameters
+are defined in the caller and must include the entirety of the
+function call that invokes this particular function. In addition, each
+universal region 'a
includes itself (that is, end('a)
) in its
+liveness constraint (i.e., 'a
must extend until the end of
+itself). In the code, these liveness constraints are setup in
+init_free_and_bound_regions
.
So, consider the first example of this section:
+fn foo<'a, 'b>(x: &'a u32, y: &'b u32) -> &'b u32 {
+ x
+}
+
+Here, returning x
requires that &'a u32 <: &'b u32
, which gives
+rise to an outlives constraint 'a: 'b
. Combined with our default liveness
+constraints we get:
'a live at {B, end('a)} // B represents the "function body"
+'b live at {B, end('b)}
+'a: 'b
+
+When we process the 'a: 'b
constraint, therefore, we will add
+end('b)
into the value for 'a
, resulting in a final value of {B, end('a), end('b)}
.
Once we have finished constraint propagation, we then enforce a
+constraint that if some universal region 'a
includes an element
+end('b)
, then 'a: 'b
must be declared in the function's bounds. If
+not, as in our example, that is an error. This check is done in the
+check_universal_regions
function, which simply iterates over all
+universal regions, inspects their final value, and tests against the
+declared UniversalRegionRelations
.
A member constraint 'm member of ['c_1..'c_N]
expresses that the
+region 'm
must be equal to some choice regions 'c_i
(for
+some i
). These constraints cannot be expressed by users, but they
+arise from impl Trait
due to its lifetime capture rules. Consider a
+function such as the following:
fn make(a: &'a u32, b: &'b u32) -> impl Trait<'a, 'b> { .. }
+
+Here, the true return type (often called the "hidden type") is only
+permitted to capture the lifetimes 'a
or 'b
. You can kind of see
+this more clearly by desugaring that impl Trait
return type into its
+more explicit form:
type MakeReturn<'x, 'y> = impl Trait<'x, 'y>;
+fn make(a: &'a u32, b: &'b u32) -> MakeReturn<'a, 'b> { .. }
+
+Here, the idea is that the hidden type must be some type that could
+have been written in place of the impl Trait<'x, 'y>
-- but clearly
+such a type can only reference the regions 'x
or 'y
(or
+'static
!), as those are the only names in scope. This limitation is
+then translated into a restriction to only access 'a
or 'b
because
+we are returning MakeReturn<'a, 'b>
, where 'x
and 'y
have been
+replaced with 'a
and 'b
respectively.
To help us explain member constraints in more detail, let's spell out
+the make
example in a bit more detail. First off, let's assume that
+you have some dummy trait:
trait Trait<'a, 'b> { }
+impl<T> Trait<'_, '_> for T { }
+
+and this is the make
function (in desugared form):
type MakeReturn<'x, 'y> = impl Trait<'x, 'y>;
+fn make(a: &'a u32, b: &'b u32) -> MakeReturn<'a, 'b> {
+ (a, b)
+}
+
+What happens in this case is that the return type will be (&'0 u32, &'1 u32)
,
+where '0
and '1
are fresh region variables. We will have the following
+region constraints:
'0 live at {L}
+'1 live at {L}
+'a: '0
+'b: '1
+'0 member of ['a, 'b, 'static]
+'1 member of ['a, 'b, 'static]
+
+Here the "liveness set" {L}
corresponds to that subset of the body
+where '0
and '1
are live -- basically the point from where the
+return tuple is constructed to where it is returned (in fact, '0
and
+'1
might have slightly different liveness sets, but that's not very
+interesting to the point we are illustrating here).
The 'a: '0
and 'b: '1
constraints arise from subtyping. When we
+construct the (a, b)
value, it will be assigned type (&'0 u32, &'1 u32)
-- the region variables reflect that the lifetimes of these
+references could be made smaller. For this value to be created from
+a
and b
, however, we do require that:
(&'a u32, &'b u32) <: (&'0 u32, &'1 u32)
+
+which means in turn that &'a u32 <: &'0 u32
and hence that 'a: '0
+(and similarly that &'b u32 <: &'1 u32
, 'b: '1
).
Note that if we ignore member constraints, the value of '0
would be
+inferred to some subset of the function body (from the liveness
+constraints, which we did not write explicitly). It would never become
+'a
, because there is no need for it too -- we have a constraint that
+'a: '0
, but that just puts a "cap" on how large '0
can grow to
+become. Since we compute the minimal value that we can, we are happy
+to leave '0
as being just equal to the liveness set. This is where
+member constraints come in.
At present, the "choice" regions from a member constraint are always lifetime
+parameters from the current function. As of October 2021,
+this falls out from the placement of impl Trait, though in the future it may not
+be the case. We take some advantage of this fact, as it simplifies the current
+code. In particular, we don't have to consider a case like '0 member of ['1, 'static]
, in which the value of both '0
and '1
are being inferred and hence
+changing. See rust-lang/rust#61773 for more information.
Member constraints are a bit more complex than other forms of
+constraints. This is because they have a "or" quality to them -- that
+is, they describe multiple choices that we must select from. E.g., in
+our example constraint '0 member of ['a, 'b, 'static]
, it might be
+that '0
is equal to 'a
, 'b
, or 'static
. How can we pick the
+correct one? What we currently do is to look for a minimal choice
+-- if we find one, then we will grow '0
to be equal to that minimal
+choice. To find that minimal choice, we take two factors into
+consideration: lower and upper bounds.
The lower bounds are those lifetimes that '0
must outlive --
+i.e., that '0
must be larger than. In fact, when it comes time to
+apply member constraints, we've already computed the lower bounds of
+'0
because we computed its minimal value (or at least, the lower
+bounds considering everything but member constraints).
Let LB
be the current value of '0
. We know then that '0: LB
must
+hold, whatever the final value of '0
is. Therefore, we can rule out
+any choice 'choice
where 'choice: LB
does not hold.
Unfortunately, in our example, this is not very helpful. The lower
+bound for '0
will just be the liveness set {L}
, and we know that
+all the lifetime parameters outlive that set. So we are left with the
+same set of choices here. (But in other examples, particularly those
+with different variance, lower bound constraints may be relevant.)
The upper bounds are those lifetimes that must outlive '0
--
+i.e., that '0
must be smaller than. In our example, this would be
+'a
, because we have the constraint that 'a: '0
. In more complex
+examples, the chain may be more indirect.
We can use upper bounds to rule out members in a very similar way to
+lower bounds. If UB is some upper bound, then we know that UB: '0
must hold, so we can rule out any choice 'choice
where UB: 'choice
does not hold.
In our example, we would be able to reduce our choice set from ['a, 'b, 'static]
to just ['a]
. This is because '0
has an upper bound
+of 'a
, and neither 'a: 'b
nor 'a: 'static
is known to hold.
(For notes on how we collect upper bounds in the implementation, see +the section below.)
+After applying lower and upper bounds, we can still sometimes have
+multiple possibilities. For example, imagine a variant of our example
+using types with the opposite variance. In that case, we would have
+the constraint '0: 'a
instead of 'a: '0
. Hence the current value
+of '0
would be {L, 'a}
. Using this as a lower bound, we would be
+able to narrow down the member choices to ['a, 'static]
because 'b: 'a
is not known to hold (but 'a: 'a
and 'static: 'a
do hold). We
+would not have any upper bounds, so that would be our final set of choices.
In that case, we apply the minimal choice rule -- basically, if
+one of our choices if smaller than the others, we can use that. In
+this case, we would opt for 'a
(and not 'static
).
This choice is consistent with the general 'flow' of region +propagation, which always aims to compute a minimal value for the +region being inferred. However, it is somewhat arbitrary.
+ +In practice, computing upper bounds is a bit inconvenient, because our
+data structures are setup for the opposite. What we do is to compute
+the reverse SCC graph (we do this lazily and cache the result) --
+that is, a graph where 'a: 'b
induces an edge SCC('b) -> SCC('a)
. Like the normal SCC graph, this is a DAG. We can then do a
+depth-first search starting from SCC('0)
in this graph. This will
+take us to all the SCCs that must outlive '0
.
One wrinkle is that, as we walk the "upper bound" SCCs, their values
+will not yet have been fully computed. However, we have already
+applied their liveness constraints, so we have some information about
+their value. In particular, for any regions representing lifetime
+parameters, their value will contain themselves (i.e., the initial
+value for 'a
includes 'a
and the value for 'b
contains 'b
). So
+we can collect all of the lifetime parameters that are reachable,
+which is precisely what we are interested in.
From time to time we have to reason about regions that we can't +concretely know. For example, consider this program:
+// A function that needs a static reference
+fn foo(x: &'static u32) { }
+
+fn bar(f: for<'a> fn(&'a u32)) {
+ // ^^^^^^^^^^^^^^^^^^^ a function that can accept **any** reference
+ let x = 22;
+ f(&x);
+}
+
+fn main() {
+ bar(foo);
+}
+
+This program ought not to type-check: foo
needs a static reference
+for its argument, and bar
wants to be given a function that
+accepts any reference (so it can call it with something on its
+stack, for example). But how do we reject it and why?
When we type-check main
, and in particular the call bar(foo)
, we
+are going to wind up with a subtyping relationship like this one:
fn(&'static u32) <: for<'a> fn(&'a u32)
+---------------- -------------------
+the type of `foo` the type `bar` expects
+
+We handle this sort of subtyping by taking the variables that are
+bound in the supertype and replacing them with
+universally quantified
+representatives, denoted like !1
here. We call these regions "placeholder
+regions" – they represent, basically, "some unknown region".
Once we've done that replacement, we have the following relation:
+fn(&'static u32) <: fn(&'!1 u32)
+
+The key idea here is that this unknown region '!1
is not related to
+any other regions. So if we can prove that the subtyping relationship
+is true for '!1
, then it ought to be true for any region, which is
+what we wanted.
So let's work through what happens next. To check if two functions are +subtypes, we check if their arguments have the desired relationship +(fn arguments are contravariant, so +we swap the left and right here):
+&'!1 u32 <: &'static u32
+
+According to the basic subtyping rules for a reference, this will be
+true if '!1: 'static
. That is – if "some unknown region !1
" outlives 'static
.
+Now, this might be true – after all, '!1
could be 'static
–
+but we don't know that it's true. So this should yield up an error (eventually).
In the previous section, we introduced the idea of a placeholder
+region, and we denoted it !1
. We call this number 1
the universe
+index. The idea of a "universe" is that it is a set of names that
+are in scope within some type or at some point. Universes are formed
+into a tree, where each child extends its parents with some new names.
+So the root universe conceptually contains global names, such as
+the lifetime 'static
or the type i32
. In the compiler, we also
+put generic type parameters into this root universe (in this sense,
+there is not just one root universe, but one per item). So consider
+this function bar
:
struct Foo { }
+
+fn bar<'a, T>(t: &'a T) {
+ ...
+}
+
+Here, the root universe would consist of the lifetimes 'static
and
+'a
. In fact, although we're focused on lifetimes here, we can apply
+the same concept to types, in which case the types Foo
and T
would
+be in the root universe (along with other global types, like i32
).
+Basically, the root universe contains all the names that
+appear free in the body of bar
.
Now let's extend bar
a bit by adding a variable x
:
fn bar<'a, T>(t: &'a T) {
+ let x: for<'b> fn(&'b u32) = ...;
+}
+
+Here, the name 'b
is not part of the root universe. Instead, when we
+"enter" into this for<'b>
(e.g., by replacing it with a placeholder), we will create
+a child universe of the root, let's call it U1:
U0 (root universe)
+│
+└─ U1 (child universe)
+
+The idea is that this child universe U1 extends the root universe U0
+with a new name, which we are identifying by its universe number:
+!1
.
Now let's extend bar
a bit by adding one more variable, y
:
fn bar<'a, T>(t: &'a T) {
+ let x: for<'b> fn(&'b u32) = ...;
+ let y: for<'c> fn(&'c u32) = ...;
+}
+
+When we enter this type, we will again create a new universe, which
+we'll call U2
. Its parent will be the root universe, and U1 will be
+its sibling:
U0 (root universe)
+│
+├─ U1 (child universe)
+│
+└─ U2 (child universe)
+
+This implies that, while in U2, we can name things from U0 or U2, but +not U1.
+Giving existential variables a universe. Now that we have this
+notion of universes, we can use it to extend our type-checker and
+things to prevent illegal names from leaking out. The idea is that we
+give each inference (existential) variable – whether it be a type or
+a lifetime – a universe. That variable's value can then only
+reference names visible from that universe. So for example if a
+lifetime variable is created in U0, then it cannot be assigned a value
+of !1
or !2
, because those names are not visible from the universe
+U0.
Representing universes with just a counter. You might be surprised +to see that the compiler doesn't keep track of a full tree of +universes. Instead, it just keeps a counter – and, to determine if +one universe can see another one, it just checks if the index is +greater. For example, U2 can see U0 because 2 >= 0. But U0 cannot see +U2, because 0 >= 2 is false.
+How can we get away with this? Doesn't this mean that we would allow +U2 to also see U1? The answer is that, yes, we would, if that +question ever arose. But because of the structure of our type +checker etc, there is no way for that to happen. In order for +something happening in the universe U1 to "communicate" with something +happening in U2, they would have to have a shared inference variable X +in common. And because everything in U1 is scoped to just U1 and its +children, that inference variable X would have to be in U0. And since +X is in U0, it cannot name anything from U1 (or U2). This is perhaps easiest +to see by using a kind of generic "logic" example:
+exists<X> {
+ forall<Y> { ... /* Y is in U1 ... */ }
+ forall<Z> { ... /* Z is in U2 ... */ }
+}
+
+Here, the only way for the two foralls to interact would be through X, +but neither Y nor Z are in scope when X is declared, so its value +cannot reference either of them.
+But where does that error come from? The way it happens is like this.
+When we are constructing the region inference context, we can tell
+from the type inference context how many placeholder variables exist
+(the InferCtxt
has an internal counter). For each of those, we
+create a corresponding universal region variable !n
and a "region
+element" placeholder(n)
. This corresponds to "some unknown set of other
+elements". The value of !n
is {placeholder(n)}
.
At the same time, we also give each existential variable a
+universe (also taken from the InferCtxt
). This universe
+determines which placeholder elements may appear in its value: For
+example, a variable in universe U3 may name placeholder(1)
, placeholder(2)
, and
+placeholder(3)
, but not placeholder(4)
. Note that the universe of an inference
+variable controls what region elements can appear in its value; it
+does not say region elements will appear.
In the region inference engine, outlives constraints have the form:
+V1: V2 @ P
+
+where V1
and V2
are region indices, and hence map to some region
+variable (which may be universally or existentially quantified). The
+P
here is a "point" in the control-flow graph; it's not important
+for this section. This variable will have a universe, so let's call
+those universes U(V1)
and U(V2)
respectively. (Actually, the only
+one we are going to care about is U(V1)
.)
When we encounter this constraint, the ordinary procedure is to start
+a DFS from P
. We keep walking so long as the nodes we are walking
+are present in value(V2)
and we add those nodes to value(V1)
. If
+we reach a return point, we add in any end(X)
elements. That part
+remains unchanged.
But then after that we want to iterate over the placeholder placeholder(x)
+elements in V2 (each of those must be visible to U(V2)
, but we
+should be able to just assume that is true, we don't have to check
+it). We have to ensure that value(V1)
outlives each of those
+placeholder elements.
Now there are two ways that could happen. First, if U(V1)
can see
+the universe x
(i.e., x <= U(V1)
), then we can just add placeholder(x)
+to value(V1)
and be done. But if not, then we have to approximate:
+we may not know what set of elements placeholder(x)
represents, but we
+should be able to compute some sort of upper bound B for it –
+some region B that outlives placeholder(x)
. For now, we'll just use
+'static
for that (since it outlives everything) – in the future, we
+can sometimes be smarter here (and in fact we have code for doing this
+already in other contexts). Moreover, since 'static
is in the root
+universe U0, we know that all variables can see it – so basically if
+we find that value(V2)
contains placeholder(x)
for some universe x
+that V1
can't see, then we force V1
to 'static
.
After all constraints have been propagated, the NLL region inference
+has one final check, where it goes over the values that wound up being
+computed for each universal region and checks that they did not get
+'too large'. In our case, we will go through each placeholder region
+and check that it contains only the placeholder(u)
element it is known to
+outlive. (Later, we might be able to know that there are relationships
+between two placeholder regions and take those into account, as we do
+for universal regions from the fn signature.)
Put another way, the "universal regions" check can be considered to be +checking constraints like:
+{placeholder(1)}: V1
+
+where {placeholder(1)}
is like a constant set, and V1 is the variable we
+made to represent the !1
region.
OK, so far so good. Now let's walk through what would happen with our +first example:
+fn(&'static u32) <: fn(&'!1 u32) @ P // this point P is not imp't here
+
+The region inference engine will create a region element domain like this:
+{ CFG; end('static); placeholder(1) }
+ --- ------------ ------- from the universe `!1`
+ | 'static is always in scope
+ all points in the CFG; not especially relevant here
+
+It will always create two universal variables, one representing
+'static
and one representing '!1
. Let's call them Vs and V1. They
+will have initial values like so:
Vs = { CFG; end('static) } // it is in U0, so can't name anything else
+V1 = { placeholder(1) }
+
+From the subtyping constraint above, we would have an outlives constraint like
+'!1: 'static @ P
+
+To process this, we would grow the value of V1 to include all of Vs:
+Vs = { CFG; end('static) }
+V1 = { CFG; end('static), placeholder(1) }
+
+At that point, constraint propagation is complete, because all the +outlives relationships are satisfied. Then we would go to the "check +universal regions" portion of the code, which would test that no +universal region grew too large.
+In this case, V1
did grow too large – it is not known to outlive
+end('static)
, nor any of the CFG – so we would report an error.
What about this subtyping relationship?
+for<'a> fn(&'a u32, &'a u32)
+ <:
+for<'b, 'c> fn(&'b u32, &'c u32)
+
+Here we would replace the bound region in the supertype with a placeholder, as before, yielding:
+for<'a> fn(&'a u32, &'a u32)
+ <:
+fn(&'!1 u32, &'!2 u32)
+
+then we instantiate the variable on the left-hand side with an
+existential in universe U2, yielding the following (?n
is a notation
+for an existential variable):
fn(&'?3 u32, &'?3 u32)
+ <:
+fn(&'!1 u32, &'!2 u32)
+
+Then we break this down further:
+&'!1 u32 <: &'?3 u32
+&'!2 u32 <: &'?3 u32
+
+and even further, yield up our region constraints:
+'!1: '?3
+'!2: '?3
+
+Note that, in this case, both '!1
and '!2
have to outlive the
+variable '?3
, but the variable '?3
is not forced to outlive
+anything else. Therefore, it simply starts and ends as the empty set
+of elements, and hence the type-check succeeds here.
(This should surprise you a little. It surprised me when I first realized it.
+We are saying that if we are a fn that needs both of its arguments to have
+the same region, we can accept being called with arguments with two
+distinct regions. That seems intuitively unsound. But in fact, it's fine, as
+I tried to explain in this issue on the Rust issue
+tracker long ago. The reason is that even if we get called with arguments of
+two distinct lifetimes, those two lifetimes have some intersection (the call
+itself), and that intersection can be our value of 'a
that we use as the
+common lifetime of our arguments. -nmatsakis)
Let's look at one last example. We'll extend the previous one to have +a return type:
+for<'a> fn(&'a u32, &'a u32) -> &'a u32
+ <:
+for<'b, 'c> fn(&'b u32, &'c u32) -> &'b u32
+
+Despite seeming very similar to the previous example, this case is going to get +an error. That's good: the problem is that we've gone from a fn that promises +to return one of its two arguments, to a fn that is promising to return the +first one. That is unsound. Let's see how it plays out.
+First, we replace the bound region in the supertype with a placeholder:
+for<'a> fn(&'a u32, &'a u32) -> &'a u32
+ <:
+fn(&'!1 u32, &'!2 u32) -> &'!1 u32
+
+Then we instantiate the subtype with existentials (in U2):
+fn(&'?3 u32, &'?3 u32) -> &'?3 u32
+ <:
+fn(&'!1 u32, &'!2 u32) -> &'!1 u32
+
+And now we create the subtyping relationships:
+&'!1 u32 <: &'?3 u32 // arg 1
+&'!2 u32 <: &'?3 u32 // arg 2
+&'?3 u32 <: &'!1 u32 // return type
+
+And finally the outlives relationships. Here, let V1, V2, and V3 be the
+variables we assign to !1
, !2
, and ?3
respectively:
V1: V3
+V2: V3
+V3: V1
+
+Those variables will have these initial values:
+V1 in U1 = {placeholder(1)}
+V2 in U2 = {placeholder(2)}
+V3 in U2 = {}
+
+Now because of the V3: V1
constraint, we have to add placeholder(1)
into V3
(and
+indeed it is visible from V3
), so we get:
V3 in U2 = {placeholder(1)}
+
+then we have this constraint V2: V3
, so we wind up having to enlarge
+V2
to include placeholder(1)
(which it can also see):
V2 in U2 = {placeholder(1), placeholder(2)}
+
+Now constraint propagation is done, but when we check the outlives
+relationships, we find that V2
includes this new element placeholder(1)
,
+so we report an error.
Two-phase borrows are a more permissive version of mutable borrows that allow
+nested method calls such as vec.push(vec.len())
. Such borrows first act as
+shared borrows in a "reservation" phase and can later be "activated" into a
+full mutable borrow.
Only certain implicit mutable borrows can be two-phase, any &mut
or ref mut
+in the source code is never a two-phase borrow. The cases where we generate a
+two-phase borrow are:
To give some examples:
++#![allow(unused)] +fn main() { +// In the source code + +// Case 1: +let mut v = Vec::new(); +v.push(v.len()); +let r = &mut Vec::new(); +r.push(r.len()); + +// Case 2: +std::mem::replace(r, vec![1, r.len()]); + +// Case 3: +let mut x = std::num::Wrapping(2); +x += x; +} +
Expanding these enough to show the two-phase borrows:
+// Case 1:
+let mut v = Vec::new();
+let temp1 = &two_phase v;
+let temp2 = v.len();
+Vec::push(temp1, temp2);
+let r = &mut Vec::new();
+let temp3 = &two_phase *r;
+let temp4 = r.len();
+Vec::push(temp3, temp4);
+
+// Case 2:
+let temp5 = &two_phase *r;
+let temp6 = vec![1, r.len()];
+std::mem::replace(temp5, temp6);
+
+// Case 3:
+let mut x = std::num::Wrapping(2);
+let temp7 = &two_phase x;
+let temp8 = x;
+std::ops::AddAssign::add_assign(temp7, temp8);
+
+Whether a borrow can be two-phase is tracked by a flag on the AutoBorrow
+after type checking, which is then converted to a BorrowKind
during MIR
+construction.
Each two-phase borrow is assigned to a temporary that is only used once. As +such we can define:
+The activation points are found using the GatherBorrows
visitor. The
+BorrowData
then holds both the reservation and activation points for the
+borrow.
Two-phase borrows are treated as if they were mutable borrows with the +following exceptions:
+is_active
) if we're at such a point
+by using the Dominators
for the MIR graph.A key component of the borrow check is the +MIR type-check. +This check walks the MIR and does a complete "type check" -- the same +kind you might find in any other language. In the process of doing +this type-check, we also uncover the region constraints that apply to +the program.
+TODO -- elaborate further? Maybe? :)
+At the start of MIR type-check, we replace all regions in the body with new unconstrained regions. +However, this would cause us to accept the following program:
++#![allow(unused)] +fn main() { +fn foo<'a>(x: &'a u32) { + let y: &'static u32 = x; +} +} +
By erasing the lifetimes in the type of y
we no longer know that it is supposed to be 'static
,
+ignoring the intentions of the user.
To deal with this we remember all places where the user explicitly mentioned a type during
+HIR type-check as CanonicalUserTypeAnnotations
.
There are two different annotations we care about:
+let y: &'static u32
results in UserType::Ty(&'static u32)
.x.foo<&'a u32, Vec<String>>
+results in UserType::TypeOf(foo_def_id, [&'a u32, Vec<String>])
.As we do not want the region inference from the HIR type-check to influence MIR typeck,
+we store the user type right after lowering it from the HIR.
+This means that it may still contain inference variables,
+which is why we are using canonical user type annotations.
+We replace all inference variables with existential bound variables instead.
+Something like let x: Vec<_>
would therefore result in exists<T> UserType::Ty(Vec<T>)
.
A pattern like let Foo(x): Foo<&'a u32>
has a user type Foo<&'a u32>
but
+the actual type of x
should only be &'a u32
. For this, we use a UserTypeProjection
.
In the MIR, we deal with user types in two slightly different ways.
+Given a MIR local corresponding to a variable in a pattern which has an explicit type annotation,
+we require the type of that local to be equal to the type of the UserTypeProjection
.
+This is directly stored in the LocalDecl
.
We also constrain the type of scrutinee expressions, e.g. the type of x
in let _: &'a u32 = x;
.
+Here T_x
only has to be a subtype of the user type, so we instead use
+StatementKind::AscribeUserType
for that.
Note that we do not directly use the user type as the MIR typechecker
+doesn't really deal with type and const inference variables. We instead store the final
+inferred_type
from the HIR type-checker. During MIR typeck, we then replace its regions
+with new nll inference vars and relate it with the actual UserType
to get the correct region
+constraints again.
After the MIR type-check, all user type annotations get discarded, as they aren't needed anymore.
+ +This page defines the best practices procedure for making bug fixes or soundness +corrections in the compiler that can cause existing code to stop compiling. This +text is based on +RFC 1589.
+From time to time, we encounter the need to make a bug fix, soundness +correction, or other change in the compiler which will cause existing code to +stop compiling. When this happens, it is important that we handle the change in +a way that gives users of Rust a smooth transition. What we want to avoid is +that existing programs suddenly stop compiling with opaque error messages: we +would prefer to have a gradual period of warnings, with clear guidance as to +what the problem is, how to fix it, and why the change was made. This RFC +describes the procedure that we have been developing for handling breaking +changes that aims to achieve that kind of smooth transition.
+One of the key points of this policy is that (a) warnings should be issued +initially rather than hard errors if at all possible and (b) every change that +causes existing code to stop compiling will have an associated tracking issue. +This issue provides a point to collect feedback on the results of that change. +Sometimes changes have unexpectedly large consequences or there may be a way to +avoid the change that was not considered. In those cases, we may decide to +change course and roll back the change, or find another solution (if warnings +are being used, this is particularly easy to do).
+Note that this RFC does not try to define when a breaking change is permitted. +That is already covered under RFC 1122. This document assumes that the +change being made is in accordance with those policies. Here is a summary of the +conditions from RFC 1122:
+Please see the RFC for full details!
+The procedure for making a breaking change is as follows (each of these steps is +described in more detail below):
+Finally, for changes to rustc_ast
that will affect plugins, the general policy
+is to batch these changes. That is discussed below in more detail.
Every breaking change should be accompanied by a dedicated tracking issue +for that change. The main text of this issue should describe the change being +made, with a focus on what users must do to fix their code. The issue should be +approachable and practical; it may make sense to direct users to an RFC or some +other issue for the full details. The issue also serves as a place where users +can comment with questions or other concerns.
+A template for these breaking-change tracking issues can be found below. An +example of how such an issue should look can be found +here.
+The issue should be tagged with (at least) B-unstable
and T-compiler
.
This is a template to use for tracking issues:
+This is the **summary issue** for the `YOUR_LINT_NAME_HERE`
+future-compatibility warning and other related errors. The goal of
+this page is describe why this change was made and how you can fix
+code that is affected by it. It also provides a place to ask questions
+or register a complaint if you feel the change should not be made. For
+more information on the policy around future-compatibility warnings,
+see our [breaking change policy guidelines][guidelines].
+
+[guidelines]: LINK_TO_THIS_RFC
+
+#### What is the warning for?
+
+*Describe the conditions that trigger the warning and how they can be
+fixed. Also explain why the change was made.**
+
+#### When will this warning become a hard error?
+
+At the beginning of each 6-week release cycle, the Rust compiler team
+will review the set of outstanding future compatibility warnings and
+nominate some of them for **Final Comment Period**. Toward the end of
+the cycle, we will review any comments and make a final determination
+whether to convert the warning into a hard error or remove it
+entirely.
+
+The best way to handle a breaking change is to begin by issuing +future-compatibility warnings. These are a special category of lint warning. +Adding a new future-compatibility warning can be done as follows.
++#![allow(unused)] +fn main() { +// 1. Define the lint in `compiler/rustc_middle/src/lint/builtin.rs`: +declare_lint! { + pub YOUR_ERROR_HERE, + Warn, + "illegal use of foo bar baz" +} + +// 2. Add to the list of HardwiredLints in the same file: +impl LintPass for HardwiredLints { + fn get_lints(&self) -> LintArray { + lint_array!( + .., + YOUR_ERROR_HERE + ) + } +} + +// 3. Register the lint in `compiler/rustc_lint/src/lib.rs`: +store.register_future_incompatible(sess, vec![ + ..., + FutureIncompatibleInfo { + id: LintId::of(YOUR_ERROR_HERE), + reference: "issue #1234", // your tracking issue here! + }, +]); + +// 4. Report the lint: +tcx.lint_node( + lint::builtin::YOUR_ERROR_HERE, + path_id, + binding.span, + format!("some helper message here")); +} +
It can often be challenging to filter out new warnings from older, pre-existing +errors. One technique that has been used in the past is to run the older code +unchanged and collect the errors it would have reported. You can then issue +warnings for any errors you would give which do not appear in that original set. +Another option is to abort compilation after the original code completes if +errors are reported: then you know that your new code will only execute when +there were no errors before.
+Crater is a bot that will compile all crates.io crates and many +public github repos with the compiler with your changes. A report will then be +generated with crates that ceased to compile with or began to compile with your +changes. Crater runs can take a few days to complete.
+We should always do a crater run to assess impact. It is polite and considerate +to at least notify the authors of affected crates the breaking change. If we can +submit PRs to fix the problem, so much the better.
+Changes that are believed to have negligible impact can go directly to issuing
+an error. One rule of thumb would be to check against crates.io
: if fewer than
+10 total affected projects are found (not root errors), we can move
+straight to an error. In such cases, we should still make the "breaking change"
+page as before, and we should ensure that the error directs users to this page.
+In other words, everything should be the same except that users are getting an
+error, and not a warning. Moreover, we should submit PRs to the affected
+projects (ideally before the PR implementing the change lands in rustc).
If the impact is not believed to be negligible (e.g., more than 10 crates are +affected), then warnings are required (unless the compiler team agrees to grant +a special exemption in some particular case). If implementing warnings is not +feasible, then we should make an aggressive strategy of migrating crates before +we land the change so as to lower the number of affected crates. Here are some +techniques for approaching this scenario:
+After a change is made, we will stabilize the change using the same process +that we use for unstable features:
+After a new release is made, we will go through the outstanding tracking +issues corresponding to breaking changes and nominate some of them for final +comment period (FCP).
+The FCP for such issues lasts for one cycle. In the final week or two of the +cycle, we will review comments and make a final determination:
+Ideally, breaking changes should have landed on the stable branch of the +compiler before they are finalized.
+ +Once we have decided to make a "future warning" into a hard error, we need a PR
+that removes the custom lint. As an example, here are the steps required to
+remove the overlapping_inherent_impls
compatibility lint. First, convert the
+name of the lint to uppercase (OVERLAPPING_INHERENT_IMPLS
) ripgrep through the
+source for that string. We will basically by converting each place where this
+lint name is mentioned (in the compiler, we use the upper-case name, and a macro
+automatically generates the lower-case string; so searching for
+overlapping_inherent_impls
would not find much).
++NOTE: these exact files don't exist anymore, but the procedure is still the same.
+
The first reference you will likely find is the lint definition in
+rustc_session/src/lint/builtin.rs
that resembles this:
+#![allow(unused)] +fn main() { +declare_lint! { + pub OVERLAPPING_INHERENT_IMPLS, + Deny, // this may also say Warning + "two overlapping inherent impls define an item with the same name were erroneously allowed" +} +} +
This declare_lint!
macro creates the relevant data structures. Remove it. You
+will also find that there is a mention of OVERLAPPING_INHERENT_IMPLS
later in
+the file as part of a lint_array!
; remove it too.
Next, you see a reference to OVERLAPPING_INHERENT_IMPLS
in
+rustc_lint/src/lib.rs
. This is defining the lint as a "future
+compatibility lint":
+#![allow(unused)] +fn main() { +FutureIncompatibleInfo { + id: LintId::of(OVERLAPPING_INHERENT_IMPLS), + reference: "issue #36889 <https://github.com/rust-lang/rust/issues/36889>", +}, +} +
Remove this too.
+In compiler/rustc_lint/src/lib.rs
there is a list of "renamed and removed lints".
+You can add this lint to the list:
+#![allow(unused)] +fn main() { +store.register_removed("overlapping_inherent_impls", "converted into hard error, see #36889"); +} +
where #36889
is the tracking issue for your lint.
Finally, the last class of references you will see are the places that actually
+trigger the lint itself (i.e., what causes the warnings to appear). These
+you do not want to delete. Instead, you want to convert them into errors. In
+this case, the add_lint
call looks like this:
+#![allow(unused)] +fn main() { +self.tcx.sess.add_lint(lint::builtin::OVERLAPPING_INHERENT_IMPLS, + node_id, + self.tcx.span_of_impl(item1).unwrap(), + msg); +} +
We want to convert this into an error. In some cases, there may be an +existing error for this scenario. In others, we will need to allocate a +fresh diagnostic code. Instructions for allocating a fresh diagnostic +code can be found here. You may want +to mention in the extended description that the compiler behavior +changed on this point, and include a reference to the tracking issue for +the change.
+Let's say that we've adopted E0592
as our code. Then we can change the
+add_lint()
call above to something like:
+#![allow(unused)] +fn main() { +struct_span_code_err!(self.dcx(), self.tcx.span_of_impl(item1).unwrap(), E0592, msg) + .emit(); +} +
Finally, run the test suite. These should be some tests that used to reference
+the overlapping_inherent_impls
lint, those will need to be updated. In
+general, if the test used to have #[deny(overlapping_inherent_impls)]
, that
+can just be removed.
./x test
+
+Open a PR. =)
+ + +The core concept in Bootstrap is a build Step
, which are chained together
+by Builder::ensure
. Builder::ensure
takes a Step
as input, and runs
+the Step
if and only if it has not already been run. Let's take a closer
+look at Step
.
Step
A Step
represents a granular collection of actions involved in the process
+of producing some artifact. It can be thought of like a rule in Makefiles.
+The Step
trait is defined as:
pub trait Step: 'static + Clone + Debug + PartialEq + Eq + Hash {
+ type Output: Clone;
+
+ const DEFAULT: bool = false;
+ const ONLY_HOSTS: bool = false;
+
+ // Required methods
+ fn run(self, builder: &Builder<'_>) -> Self::Output;
+ fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_>;
+
+ // Provided method
+ fn make_run(_run: RunConfig<'_>) { ... }
+}
+
+run
is the function that is responsible for doing the work.
+Builder::ensure
invokes run
.should_run
is the command-line interface, which determines if an invocation
+such as x build foo
should run a given Step
. In a "default" context
+where no paths are provided, then make_run
is called directly.make_run
is invoked only for things directly asked via the CLI and not
+for steps which are dependencies of other steps.There's a couple of preliminary steps before core Bootstrap code is reached:
+make
: ./x
or ./x.ps1
or make
x.py
src/bootstrap/bootstrap.py
src/bootstrap/src/bin/main.rs
See src/bootstrap/README.md +for a more specific description of the implementation details.
+ +Bootstrapping is the process of using a compiler to compile itself. +More accurately, it means using an older compiler to compile a newer version +of the same compiler.
+This raises a chicken-and-egg paradox: where did the first compiler come from? +It must have been written in a different language. In Rust's case it was +written in OCaml. However it was abandoned long ago and the +only way to build a modern version of rustc is a slightly less modern +version.
+This is exactly how x.py
works: it downloads the current beta release of
+rustc, then uses it to compile the new compiler.
In this section, we give a high-level overview of +what Bootstrap does, followed by a high-level +introduction to how Bootstrap does it.
+ +bootstrap
stdout
+
+Bootstrapping is the process of using a compiler to compile itself. +More accurately, it means using an older compiler to compile a newer version of +the same compiler.
+This raises a chicken-and-egg paradox: where did the first compiler come from?
+It must have been written in a different language. In Rust's case it was
+written in OCaml. However it was abandoned long ago and the
+only way to build a modern version of rustc
is a slightly less modern version.
This is exactly how ./x.py
works: it downloads the current beta release of
+rustc
, then uses it to compile the new compiler.
Note that this documentation mostly covers user-facing information. See +bootstrap/README.md to read about bootstrap internals.
+Compiling rustc
is done in stages. Here's a diagram, adapted from Jynn
+Nelson's talk on bootstrapping at RustConf 2022, with
+detailed explanations below.
The A
, B
, C
, and D
show the ordering of the stages of bootstrapping.
+Blue nodes are
+downloaded, yellow
+nodes are built with the stage0
compiler, and green nodes are built with the stage1
+compiler.
graph TD + s0c["stage0 compiler (1.63)"]:::downloaded -->|A| s0l("stage0 std (1.64)"):::with-s0c; + s0c & s0l --- stepb[ ]:::empty; + stepb -->|B| s0ca["stage0 compiler artifacts (1.64)"]:::with-s0c; + s0ca -->|copy| s1c["stage1 compiler (1.64)"]:::with-s0c; + s1c -->|C| s1l("stage1 std (1.64)"):::with-s1c; + s1c & s1l --- stepd[ ]:::empty; + stepd -->|D| s1ca["stage1 compiler artifacts (1.64)"]:::with-s1c; + s1ca -->|copy| s2c["stage2 compiler"]:::with-s1c; + + classDef empty width:0px,height:0px; + classDef downloaded fill: lightblue; + classDef with-s0c fill: yellow; + classDef with-s1c fill: lightgreen; ++
The stage0 compiler is usually the current beta rustc
compiler and its
+associated dynamic libraries, which ./x.py
will download for you. (You can
+also configure ./x.py
to use something else.)
The stage0 compiler is then used only to compile src/bootstrap
,
+library/std
, and compiler/rustc
. When assembling the libraries and
+binaries that will become the stage1 rustc
compiler, the freshly compiled
+std
and rustc
are used. There are two concepts at play here: a compiler
+(with its set of dependencies) and its 'target' or 'object' libraries (std
and
+rustc
). Both are staged, but in a staggered manner.
The rustc source code is then compiled with the stage0
compiler to produce the
+stage1
compiler.
We then rebuild our stage1
compiler with itself to produce the stage2
+compiler.
In theory, the stage1
compiler is functionally identical to the stage2
+compiler, but in practice there are subtle differences. In particular, the
+stage1
compiler itself was built by stage0
and hence not by the source in
+your working directory. This means that the ABI generated by the stage0
+compiler may not match the ABI that would have been made by the stage1
+compiler, which can cause problems for dynamic libraries, tests, and tools using
+rustc_private
.
Note that the proc_macro
crate avoids this issue with a C
FFI layer called
+proc_macro::bridge
, allowing it to be used with stage1
.
The stage2
compiler is the one distributed with rustup
and all other install
+methods. However, it takes a very long time to build because one must first
+build the new compiler with an older compiler and then use that to build the new
+compiler with itself. For development, you usually only want the stage1
+compiler, which you can build with ./x build library
. See Building the
+compiler.
Stage 3 is optional. To sanity check our new compiler we can build the libraries
+with the stage2
compiler. The result ought to be identical to before, unless
+something has broken.
The script ./x
tries to be helpful and pick the stage you most likely meant
+for each subcommand. These defaults are as follows:
check
: --stage 0
doc
: --stage 0
build
: --stage 1
test
: --stage 1
dist
: --stage 2
install
: --stage 2
bench
: --stage 2
You can always override the stage by passing --stage N
explicitly.
For more information about stages, see +below.
+Since the build system uses the current beta compiler to build a stage1
+bootstrapping compiler, the compiler source code can't use some features until
+they reach beta (because otherwise the beta compiler doesn't support them). On
+the other hand, for compiler intrinsics and internal features, the
+features have to be used. Additionally, the compiler makes heavy use of
+nightly
features (#![feature(...)]
). How can we resolve this problem?
There are two methods used:
+--cfg bootstrap
when building with stage0
, so we
+can use cfg(not(bootstrap))
to only use features when built with stage1
.
+Setting --cfg bootstrap
in this way is used for features that were just
+stabilized, which require #![feature(...)]
when built with stage0
, but
+not for stage1
.RUSTC_BOOTSTRAP=1
. This special variable means to
+break the stability guarantees of Rust: allowing use of #![feature(...)]
+with a compiler that's not nightly
. Setting RUSTC_BOOTSTRAP=1
should
+never be used except when bootstrapping the compiler.This is a detailed look into the separate bootstrap stages.
+The convention ./x
uses is that:
--stage N
flag means to run the stage N compiler (stageN/rustc
).Anything you can build with ./x
is a build artifact. Build artifacts
+include, but are not limited to:
stage0-rustc/rustc-main
stage0-sysroot/rustlib/libstd-6fae108520cf72fe.so
stage0-sysroot/rustlib/libstd-6fae108520cf72fe.rlib
doc/std
./x test tests/ui
means to build the stage1
compiler and run compiletest
+on it. If you're working on the compiler, this is normally the test command
+you want../x test --stage 0 library/std
means to run tests on the standard library
+without building rustc
from source ('build with stage0
, then test the
+artifacts'). If you're working on the standard library, this is normally the
+test command you want../x build --stage 0
means to build with the beta rustc
../x doc --stage 0
means to document using the beta rustdoc
../x test --stage 0 tests/ui
is not useful: it runs tests on the beta
+compiler and doesn't build rustc
from source. Use test tests/ui
instead,
+which builds stage1
from source../x test --stage 0 compiler/rustc
builds the compiler but runs no tests:
+it's running cargo test -p rustc
, but cargo
doesn't understand Rust's
+tests. You shouldn't need to use this, use test
instead (without arguments)../x build --stage 0 compiler/rustc
builds the compiler, but does not build
+libstd
or even libcore
. Most of the time, you'll want ./x build library
+instead, which allows compiling programs without needing to define lang items.Note that build --stage N compiler/rustc
does not build the stage N
+compiler: instead it builds the stage N+1 compiler using the stage N compiler.
In short, stage 0 uses the stage0
compiler to create stage0
artifacts which
+will later be uplifted to be the stage1 compiler.
In each stage, two major steps are performed:
+std
is compiled by the stage N compiler.std
is linked to programs built by the stage N compiler, including the
+stage N artifacts (stage N+1 compiler).This is somewhat intuitive if one thinks of the stage N artifacts as "just"
+another program we are building with the stage N compiler: build --stage N compiler/rustc
is linking the stage N artifacts to the std
built by the stage
+N compiler.
std
Note that there are two std
libraries in play here:
stageN/rustc
, which was built by stage N-1 (stage
+N-1 std
)stageN/rustc
, which was built
+by stage N (stage N std
).Stage N std
is pretty much necessary for any useful work with the stage N
+compiler. Without it, you can only compile programs with #![no_core]
-- not
+terribly useful!
The reason these need to be different is because they aren't necessarily
+ABI-compatible: there could be new layout optimizations, changes to MIR
, or
+other changes to Rust metadata on nightly
that aren't present in beta.
This is also where --keep-stage 1 library/std
comes into play. Since most
+changes to the compiler don't actually change the ABI, once you've produced a
+std
in stage1
, you can probably just reuse it with a different compiler. If
+the ABI hasn't changed, you're good to go, no need to spend time recompiling
+that std
. The flag --keep-stage
simply instructs the build script to assumes
+the previous compile is fine and copies those artifacts into the appropriate
+place, skipping the cargo
invocation.
Cross-compiling is the process of compiling code that will run on another
+architecture. For instance, you might want to build an ARM version of rustc
+using an x86 machine. Building stage2
std
is different when you are
+cross-compiling.
This is because ./x
uses the following logic: if HOST
and TARGET
are the
+same, it will reuse stage1
std
for stage2
! This is sound because stage1
+std
was compiled with the stage1
compiler, i.e. a compiler using the source
+code you currently have checked out. So it should be identical (and therefore
+ABI-compatible) to the std
that stage2/rustc
would compile.
However, when cross-compiling, stage1
std
will only run on the host. So the
+stage2
compiler has to recompile std
for the target.
(See in the table how stage2
only builds non-host std
targets).
cfg(bootstrap)
?For docs on cfg(bootstrap)
itself, see Complications of
+Bootstrapping.
The rustc
generated by the stage0
compiler is linked to the freshly-built
+std
, which means that for the most part only std
needs to be cfg
-gated, so
+that rustc
can use features added to std
immediately after their addition,
+without need for them to get into the downloaded beta
compiler.
Note this is different from any other Rust program: stage1
rustc
is built by
+the beta compiler, but using the master version of libstd
!
The only time rustc
uses cfg(bootstrap)
is when it adds internal lints that
+use diagnostic items, or when it uses unstable library features that were
+recently changed.
When you build a project with cargo
, the build artifacts for dependencies are
+normally stored in target/debug/deps
. This only contains dependencies cargo
+knows about; in particular, it doesn't have the standard library. Where do std
+or proc_macro
come from? They comes from the sysroot, the root of a number
+of directories where the compiler loads build artifacts at runtime. The
+sysroot
doesn't just store the standard library, though - it includes anything
+that needs to be loaded at runtime. That includes (but is not limited to):
libstd
/libtest
/libproc_macro
.rustc_private
. In-tree these are
+always present; out of tree, you need to install rustc-dev
with rustup
.libLLVM.so
for the LLVM project. In-tree this is either
+built from source or downloaded from CI; out-of-tree, you need to install
+llvm-tools-preview
with rustup
.All the artifacts listed so far are compiler runtime dependencies. You can see
+them with rustc --print sysroot
:
$ ls $(rustc --print sysroot)/lib
+libchalk_derive-0685d79833dc9b2b.so libstd-25c6acf8063a3802.so
+libLLVM-11-rust-1.50.0-nightly.so libtest-57470d2aa8f7aa83.so
+librustc_driver-4f0cc9f50e53f0ba.so libtracing_attributes-e4be92c35ab2a33b.so
+librustc_macros-5f0ec4a119c6ac86.so rustlib
+
+There are also runtime dependencies for the standard library! These are in
+lib/rustlib/
, not lib/
directly.
$ ls $(rustc --print sysroot)/lib/rustlib/x86_64-unknown-linux-gnu/lib | head -n 5
+libaddr2line-6c8e02b8fedc1e5f.rlib
+libadler-9ef2480568df55af.rlib
+liballoc-9c4002b5f79ba0e1.rlib
+libcfg_if-512eb53291f6de7e.rlib
+libcompiler_builtins-ef2408da76957905.rlib
+
+Directory lib/rustlib/
includes libraries like hashbrown
and cfg_if
, which
+are not part of the public API of the standard library, but are used to
+implement it. Also lib/rustlib/
is part of the search path for linkers, but
+lib
will never be part of the search path.
-Z force-unstable-if-unmarked
Since lib/rustlib/
is part of the search path we have to be careful about
+which crates are included in it. In particular, all crates except for the
+standard library are built with the flag -Z force-unstable-if-unmarked
, which
+means that you have to use #![feature(rustc_private)]
in order to load it (as
+opposed to the standard library, which is always available).
The -Z force-unstable-if-unmarked
flag has a variety of purposes to help
+enforce that the correct crates are marked as unstable
. It was introduced
+primarily to allow rustc and the standard library to link to arbitrary crates on
+crates.io which do not themselves use staged_api
. rustc
also relies on this
+flag to mark all of its crates as unstable
with the rustc_private
feature so
+that each crate does not need to be carefully marked with unstable
.
This flag is automatically applied to all of rustc
and the standard library by
+the bootstrap scripts. This is needed because the compiler and all of its
+dependencies are shipped in sysroot
to all users.
This flag has the following effects:
+unstable
" with the rustc_private
feature if it is not
+itself marked as stable
or unstable
.#![feature(rustc_private)]
+attribute to use other unstable
crates. However, that would make it
+impossible for a crate from crates.io to access its own dependencies since
+that crate won't have a feature(rustc_private)
attribute, but everything
+is compiled with -Z force-unstable-if-unmarked
.Code which does not use -Z force-unstable-if-unmarked
should include the
+#![feature(rustc_private)]
crate attribute to access these forced-unstable
+crates. This is needed for things which link rustc
its self, such as MIRI
or
+clippy
.
You can find more discussion about sysroots in:
+extern crate
for dependencies loaded
+from sysroot
bootstrap
Conveniently ./x
allows you to pass stage-specific flags to rustc
and
+cargo
when bootstrapping. The RUSTFLAGS_BOOTSTRAP
environment variable is
+passed as RUSTFLAGS
to the bootstrap stage (stage0
), and
+RUSTFLAGS_NOT_BOOTSTRAP
is passed when building artifacts for later stages.
+RUSTFLAGS
will work, but also affects the build of bootstrap
itself, so it
+will be rare to want to use it. Finally, MAGIC_EXTRA_RUSTFLAGS
bypasses the
+cargo
cache to pass flags to rustc without recompiling all dependencies.
RUSTDOCFLAGS
, RUSTDOCFLAGS_BOOTSTRAP
and RUSTDOCFLAGS_NOT_BOOTSTRAP
are
+analogous to RUSTFLAGS
, but for rustdoc
.CARGOFLAGS
will pass arguments to cargo itself (e.g. --timings
).
+CARGOFLAGS_BOOTSTRAP
and CARGOFLAGS_NOT_BOOTSTRAP
work analogously to
+RUSTFLAGS_BOOTSTRAP
.--test-args
will pass arguments through to the test runner. For tests/ui
,
+this is compiletest
. For unit tests and doc tests this is the libtest
+runner.Most test runner accept --help
, which you can use to find out the options
+accepted by the runner.
During bootstrapping, there are a bunch of compiler-internal environment
+variables that are used. If you are trying to run an intermediate version of
+rustc
, sometimes you may need to set some of these environment variables
+manually. Otherwise, you get an error like the following:
thread 'main' panicked at 'RUSTC_STAGE was not set: NotPresent', library/core/src/result.rs:1165:5
+
+If ./stageN/bin/rustc
gives an error about environment variables, that usually
+means something is quite wrong -- such as you're trying to compile rustc
or
+std
or something which depends on environment variables. In the unlikely case
+that you actually need to invoke rustc
in such a situation, you can tell the
+bootstrap shim to print all env
variables by adding -vvv
to your x
+command.
Finally, bootstrap makes use of the cc-rs crate which has its own
+method of configuring C
compilers and C
flags via environment
+variables.
stdout
In this part, we will investigate the build command's stdout
in an action
+(similar, but more detailed and complete documentation compare to topic above).
+When you execute x build --dry-run
command, the build output will be something
+like the following:
Building stage0 library artifacts (x86_64-unknown-linux-gnu -> x86_64-unknown-linux-gnu)
+Copying stage0 library from stage0 (x86_64-unknown-linux-gnu -> x86_64-unknown-linux-gnu / x86_64-unknown-linux-gnu)
+Building stage0 compiler artifacts (x86_64-unknown-linux-gnu -> x86_64-unknown-linux-gnu)
+Copying stage0 rustc from stage0 (x86_64-unknown-linux-gnu -> x86_64-unknown-linux-gnu / x86_64-unknown-linux-gnu)
+Assembling stage1 compiler (x86_64-unknown-linux-gnu)
+Building stage1 library artifacts (x86_64-unknown-linux-gnu -> x86_64-unknown-linux-gnu)
+Copying stage1 library from stage1 (x86_64-unknown-linux-gnu -> x86_64-unknown-linux-gnu / x86_64-unknown-linux-gnu)
+Building stage1 tool rust-analyzer-proc-macro-srv (x86_64-unknown-linux-gnu)
+Building rustdoc for stage1 (x86_64-unknown-linux-gnu)
+
+These steps use the provided (downloaded, usually) compiler to compile the local +Rust source into libraries we can use.
+This copies the library and compiler artifacts from cargo
into
+stage0-sysroot/lib/rustlib/{target-triple}/lib
This copies the libraries we built in "building stage0
... artifacts" into the
+stage1
compiler's lib/
directory. These are the host libraries that the
+compiler itself uses to run. These aren't actually used by artifacts the new
+compiler generates. This step also copies the rustc
and rustdoc
binaries we
+generated into build/$HOST/stage/bin
.
The stage1/bin/rustc
is a fully functional compiler, but it doesn't yet have
+any libraries to link built binaries or libraries to. The next 3 steps will
+provide those libraries for it; they are mostly equivalent to constructing the
+stage1/bin
compiler so we don't go through them individually here.
You might want to build and package up the compiler for distribution. +You’ll want to run this command to do it:
+./x dist
+
+You might want to prefer installing Rust (and tools configured in your configuration) +by building from source. If so, you want to run this command:
+./x install
+
+Note: If you are testing out a modification to a compiler, you might
+want to build the compiler (with ./x build
) then create a toolchain as
+discussed in here.
For example, if the toolchain you created is called "foo", you would then
+invoke it with rustc +foo ...
(where ... represents the rest of the arguments).
Instead of installing Rust (and tools in your config file) globally, you can set DESTDIR
+environment variable to change the installation path. If you want to set installation paths
+more dynamically, you should prefer install options in your config file to achieve that.
This chapter describes how to build documentation of toolchain components, +like the standard library (std) or the compiler (rustc).
+Document everything
+This uses rustdoc
from the beta toolchain,
+so will produce (slightly) different output to stage 1 rustdoc,
+as rustdoc is under active development:
./x doc
+
+If you want to be sure the documentation looks the same as on CI:
+./x doc --stage 1
+
+This ensures that (current) rustdoc gets built, +then that is used to document the components.
+Much like running individual tests or building specific components, +you can build just the documentation you want:
+./x doc src/doc/book
+./x doc src/doc/nomicon
+./x doc compiler library
+
+See the nightly docs index page for a full list of books.
+Document internal rustc items
+Compiler documentation is not built by default.
+To create it by default with x doc
, modify config.toml
:
[build]
+compiler-docs = true
+
+Note that when enabled, +documentation for internal compiler items will also be built.
+NOTE: The documentation for the compiler is found at this link.
+x.py
?
+
+config.toml
x
commands
+
+x
commands
+
+The compiler is built using a tool called x.py
. You will need to
+have Python installed to run it.
For a less in-depth quick-start of getting the compiler running, see quickstart.
+The main repository is rust-lang/rust
. This contains the compiler,
+the standard library (including core
, alloc
, test
, proc_macro
, etc),
+and a bunch of tools (e.g. rustdoc
, the bootstrapping infrastructure, etc).
The very first step to work on rustc
is to clone the repository:
git clone https://github.com/rust-lang/rust.git
+cd rust
+
+Due to the size of the repository, cloning on a slower internet connection can take a long time, +and requires disk space to store the full history of every file and directory. +Instead, it is possible to tell git to perform a partial clone, which will only fully retrieve +the current file contents, but will automatically retrieve further file contents when you, e.g., +jump back in the history. +All git commands will continue to work as usual, at the price of requiring an internet connection +to visit not-yet-loaded points in history.
+git clone --filter='blob:none' https://github.com/rust-lang/rust.git
+cd rust
+
+++NOTE: This link +describes this type of checkout in more detail, and also compares it to other modes, such as +shallow cloning.
+
An older alternative to partial clones is to use shallow clone the repository instead.
+To do so, you can use the --depth N
option with the git clone
command.
+This instructs git
to perform a "shallow clone", cloning the repository but truncating it to
+the last N
commits.
Passing --depth 1
tells git
to clone the repository but truncate the history to the latest
+commit that is on the master
branch, which is usually fine for browsing the source code or
+building the compiler.
git clone --depth 1 https://github.com/rust-lang/rust.git
+cd rust
+
+++NOTE: A shallow clone limits which
+git
commands can be run. +If you intend to work on and contribute to the compiler, it is +generally recommended to fully clone the repository as shown above, +or to perform a partial clone instead.For example,
+git bisect
andgit blame
require access to the commit history, +so they don't work if the repository was cloned with--depth 1
.
x.py
?x.py
is the build tool for the rust
repository. It can build docs, run tests, and compile the
+compiler and standard library.
This chapter focuses on the basics to be productive, but
+if you want to learn more about x.py
, read this chapter.
Also, using x
rather than x.py
is recommended as:
+++
./x
is the most likely to work on every system (on Unix it runs the shell script +that does python version detection, on Windows it will probably run the +powershell script - certainly less likely to break than./x.py
which often just +opens the file in an editor).1
(You can find the platform related scripts around the x.py
, like x.ps1
)
Notice that this is not absolute. For instance, using Nushell in VSCode on Win10,
+typing x
or ./x
still opens x.py
in an editor rather than invoking the program. :)
In the rest of this guide, we use x
rather than x.py
directly. The following
+command:
./x check
+
+could be replaced by:
+./x.py check
+
+x.py
The x.py
command can be run directly on most Unix systems in the following format:
./x <subcommand> [flags]
+
+This is how the documentation and examples assume you are running x.py
.
+Some alternative ways are:
# On a Unix shell if you don't have the necessary `python3` command
+./x <subcommand> [flags]
+
+# In Windows Powershell (if powershell is configured to run scripts)
+./x <subcommand> [flags]
+./x.ps1 <subcommand> [flags]
+
+# On the Windows Command Prompt (if .py files are configured to run Python)
+x.py <subcommand> [flags]
+
+# You can also run Python yourself, e.g.:
+python x.py <subcommand> [flags]
+
+On Windows, the Powershell commands may give you an error that looks like this:
+PS C:\Users\vboxuser\rust> ./x
+./x : File C:\Users\vboxuser\rust\x.ps1 cannot be loaded because running scripts is disabled on this system. For more
+information, see about_Execution_Policies at https:/go.microsoft.com/fwlink/?LinkID=135170.
+At line:1 char:1
++ ./x
++ ~~~
+ + CategoryInfo : SecurityError: (:) [], PSSecurityException
+ + FullyQualifiedErrorId : UnauthorizedAccess
+
+You can avoid this error by allowing powershell to run local scripts:
+Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
+
+x.py
slightly more convenientlyThere is a binary that wraps x.py
called x
in src/tools/x
. All it does is
+run x.py
, but it can be installed system-wide and run from any subdirectory
+of a checkout. It also looks up the appropriate version of python
to use.
You can install it with cargo install --path src/tools/x
.
To clarify that this is another global installed binary util, which is
+similar to the one declared in section What is x.py
, but
+it works as an independent process to execute the x.py
rather than calling the
+shell to run the platform related scripts.
config.toml
To start, run ./x setup
and select the compiler
defaults. This will do some initialization
+and create a config.toml
for you with reasonable defaults. If you use a different default (which
+you'll likely want to do if you want to contribute to an area of rust other than the compiler, such
+as rustdoc), make sure to read information about that default (located in src/bootstrap/defaults
)
+as the build process may be different for other defaults.
Alternatively, you can write config.toml
by hand. See config.example.toml
for all the available
+settings and explanations of them. See src/bootstrap/defaults
for common settings to change.
If you have already built rustc
and you change settings related to LLVM, then you may have to
+execute rm -rf build
for subsequent configuration changes to take effect. Note that ./x clean
will not cause a rebuild of LLVM.
x
commandsHere are the basic invocations of the x
commands most commonly used when
+working on rustc
, std
, rustdoc
, and other tools.
Command | When to use it |
---|---|
./x check | Quick check to see if most things compile; rust-analyzer can run this automatically for you |
./x build | Builds rustc , std , and rustdoc |
./x test | Runs all tests |
./x fmt | Formats all code |
As written, these commands are reasonable starting points. However, there are
+additional options and arguments for each of them that are worth learning for
+serious development work. In particular, ./x build
and ./x test
+provide many ways to compile or test a subset of the code, which can save a lot
+of time.
Also, note that x
supports all kinds of path suffixes for compiler
, library
,
+and src/tools
directories. So, you can simply run x test tidy
instead of
+x test src/tools/tidy
. Or, x build std
instead of x build library/std
.
See the chapters on +testing and rustdoc for more details.
+Note that building will require a relatively large amount of storage space. +You may want to have upwards of 10 or 15 gigabytes available to build the compiler.
+Once you've created a config.toml
, you are now ready to run
+x
. There are a lot of options here, but let's start with what is
+probably the best "go to" command for building a local compiler:
./x build library
+
+This may look like it only builds the standard library, but that is not the case. +What this command does is the following:
+std
using the stage0 compilerrustc
using the stage0 compiler
+std
using the stage1 compilerThis final product (stage1 compiler + libs built using that compiler)
+is what you need to build other Rust programs (unless you use #![no_std]
or
+#![no_core]
).
You will probably find that building the stage1 std
is a bottleneck for you,
+but fear not, there is a (hacky) workaround...
+see the section on avoiding rebuilds for std.
Sometimes you don't need a full build. When doing some kind of
+"type-based refactoring", like renaming a method, or changing the
+signature of some function, you can use ./x check
instead for a much faster build.
Note that this whole command just gives you a subset of the full rustc
+build. The full rustc
build (what you get with ./x build --stage 2 compiler/rustc
) has quite a few more steps:
rustc
with the stage1 compiler.
+std
with stage2 compiler.librustdoc
and a bunch of other things with the stage2 compiler.You almost never need to do this.
+If you are working on the standard library, you probably don't need to build +the compiler unless you are planning to use a recently added nightly feature. +Instead, you can just build using the bootstrap compiler.
+./x build --stage 0 library
+
+If you choose the library
profile when running x setup
, you can omit --stage 0
(it's the
+default).
Once you have successfully built rustc
, you will have created a bunch
+of files in your build
directory. In order to actually run the
+resulting rustc
, we recommend creating rustup toolchains. The first
+one will run the stage1 compiler (which we built above). The second
+will execute the stage2 compiler (which we did not build, but which
+you will likely need to build at some point; for example, if you want
+to run the entire test suite).
rustup toolchain link stage0 build/host/stage0-sysroot # beta compiler + stage0 std
+rustup toolchain link stage1 build/host/stage1
+rustup toolchain link stage2 build/host/stage2
+
+Now you can run the rustc
you built with. If you run with -vV
, you
+should see a version number ending in -dev
, indicating a build from
+your local environment:
$ rustc +stage1 -vV
+rustc 1.48.0-dev
+binary: rustc
+commit-hash: unknown
+commit-date: unknown
+host: x86_64-unknown-linux-gnu
+release: 1.48.0-dev
+LLVM version: 11.0
+
+The rustup toolchain points to the specified toolchain compiled in your build
directory,
+so the rustup toolchain will be updated whenever x build
or x test
are run for
+that toolchain/stage.
Note: the toolchain we've built does not include cargo
. In this case, rustup
will
+fall back to using cargo
from the installed nightly
, beta
, or stable
toolchain
+(in that order). If you need to use unstable cargo
flags, be sure to run
+rustup install nightly
if you haven't already. See the
+rustup documentation on custom toolchains.
Note: rust-analyzer and IntelliJ Rust plugin use a component called
+rust-analyzer-proc-macro-srv
to work with proc macros. If you intend to use a
+custom toolchain for a project (e.g. via rustup override set stage1
) you may
+want to build this component:
./x build proc-macro-srv-cli
+
+To produce a compiler that can cross-compile for other targets,
+pass any number of target
flags to x build
.
+For example, if your host platform is x86_64-unknown-linux-gnu
+and your cross-compilation target is wasm32-wasip1
, you can build with:
./x build --target x86_64-unknown-linux-gnu,wasm32-wasip1
+
+Note that if you want the resulting compiler to be able to build crates that
+involve proc macros or build scripts, you must be sure to explicitly build target support for the
+host platform (in this case, x86_64-unknown-linux-gnu
).
If you want to always build for other targets without needing to pass flags to x build
,
+you can configure this in the [build]
section of your config.toml
like so:
[build]
+target = ["x86_64-unknown-linux-gnu", "wasm32-wasip1"]
+
+Note that building for some targets requires having external dependencies installed
+(e.g. building musl targets requires a local copy of musl).
+Any target-specific configuration (e.g. the path to a local copy of musl)
+will need to be provided by your config.toml
.
+Please see config.example.toml
for information on target-specific configuration keys.
For examples of the complete configuration necessary to build a target, please visit +the rustc book, +select any target under the "Platform Support" heading on the left, +and see the section related to building a compiler for that target. +For targets without a corresponding page in the rustc book, +it may be useful to inspect the Dockerfiles +that the Rust infrastructure itself uses to set up and configure cross-compilation.
+If you have followed the directions from the prior section on creating a rustup toolchain, +then once you have built your compiler you will be able to use it to cross-compile like so:
+cargo +stage1 build --target wasm32-wasip1
+
+x
commandsHere are a few other useful x
commands. We'll cover some of them in detail
+in other sections:
./x build
– builds everything using the stage 1 compiler,
+not just up to std
./x build --stage 2
– builds everything with the stage 2 compiler including
+rustdoc
./x test library/std
– runs the unit tests and integration tests from std
./x test tests/ui
– runs the ui
test suite./x test tests/ui/const-generics
- runs all the tests in
+the const-generics/
subdirectory of the ui
test suite./x test tests/ui/const-generics/const-types.rs
- runs
+the single test const-types.rs
from the ui
test suiteSometimes you need to start fresh, but this is normally not the case. +If you need to run this then bootstrap is most likely not acting right and +you should file a bug as to what is going wrong. If you do need to clean +everything up then you only need to run one command!
+./x clean
+
+rm -rf build
works too, but then you have to rebuild LLVM, which can take
+a long time even on fast computers.
Building the compiler (especially if beyond stage 1) can require significant amounts of free disk
+space, possibly around 100GB. This is compounded if you have a separate build directory for
+rust-analyzer (e.g. build-rust-analyzer
). This is easy to hit with dev-desktops which have a set
+disk
+quota
+for each user, but this also applies to local development as well. Occassionally, you may need to:
build/
directory.build-rust-analyzer/
directory (if you have a separate rust-analyzer build directory).cargo-bisect-rustc
. You can check which toolchains
+are installed with rustup toolchain list
.issue#1707
+These are a set of steps to add support for a new target. There are +numerous end states and paths to get there, so not all sections may be +relevant to your desired goal.
+For very new targets, you may need to use a different fork of LLVM
+than what is currently shipped with Rust. In that case, navigate to
+the src/llvm-project
git submodule (you might need to run ./x check
at least once so the submodule is updated), check out the
+appropriate commit for your fork, then commit that new submodule
+reference in the main Rust repository.
An example would be:
+cd src/llvm-project
+git remote add my-target-llvm some-llvm-repository
+git checkout my-target-llvm/my-branch
+cd ..
+git add llvm-project
+git commit -m 'Use my custom LLVM'
+
+If you have a local LLVM checkout that is already built, you may be +able to configure Rust to treat your build as the system LLVM to avoid +redundant builds.
+You can tell Rust to use a pre-built version of LLVM using the target
section
+of config.toml
:
[target.x86_64-unknown-linux-gnu]
+llvm-config = "/path/to/llvm/llvm-7.0.1/bin/llvm-config"
+
+If you are attempting to use a system LLVM, we have observed the following paths +before, though they may be different from your system:
+/usr/bin/llvm-config-8
/usr/lib/llvm-8/bin/llvm-config
Note that you need to have the LLVM FileCheck
tool installed, which is used
+for codegen tests. This tool is normally built with LLVM, but if you use your
+own preinstalled LLVM, you will need to provide FileCheck
in some other way.
+On Debian-based systems, you can install the llvm-N-tools
package (where N
+is the LLVM version number, e.g. llvm-8-tools
). Alternately, you can specify
+the path to FileCheck
with the llvm-filecheck
config item in config.toml
+or you can disable codegen test with the codegen-tests
item in config.toml
.
You should start with a target JSON file. You can see the specification
+for an existing target using --print target-spec-json
:
rustc -Z unstable-options --target=wasm32-unknown-unknown --print target-spec-json
+
+Save that JSON to a file and modify it as appropriate for your target.
+Once you have filled out a JSON specification and been able to compile +somewhat successfully, you can copy the specification into the +compiler itself.
+You will need to add a line to the big table inside of the
+supported_targets
macro in the rustc_target::spec
module. You
+will then add a corresponding file for your new target containing a
+target
function.
Look for existing targets to use as examples.
+After adding your target to the rustc_target
crate you may want to add
+core
, std
, ... with support for your new target. In that case you will
+probably need access to some target_*
cfg. Unfortunately when building with
+stage0 (the beta compiler), you'll get an error that the target cfg is
+unexpected because stage0 doesn't know about the new target specification and
+we pass --check-cfg
in order to tell it to check.
To fix the errors you will need to manually add the unexpected value to the
+different Cargo.toml
in library/{std,alloc,core}/Cargo.toml
. Here is an
+example for adding NEW_TARGET_ARCH
as target_arch
:
library/std/Cargo.toml
:
[lints.rust.unexpected_cfgs]
+ level = "warn"
+ check-cfg = [
+ 'cfg(bootstrap)',
+- 'cfg(target_arch, values("xtensa"))',
++ # #[cfg(bootstrap)] NEW_TARGET_ARCH
++ 'cfg(target_arch, values("xtensa", "NEW_TARGET_ARCH"))',
+
+To use this target in bootstrap, we need to explicitly add the target triple to the STAGE0_MISSING_TARGETS
+list in src/bootstrap/src/core/sanity.rs
. This is necessary because the default compiler bootstrap uses does
+not recognize the new target we just added. Therefore, it should be added to STAGE0_MISSING_TARGETS
so that the
+bootstrap is aware that this target is not yet supported by the stage0 compiler.
const STAGE0_MISSING_TARGETS: &[&str] = &[
++ "NEW_TARGET_TRIPLE"
+];
+
+You may need to make changes to crates that the compiler depends on,
+such as libc
or cc
. If so, you can use Cargo's
+[patch]
ability. For example, if you want to use an
+unreleased version of libc
, you can add it to the top-level
+Cargo.toml
file:
diff --git a/Cargo.toml b/Cargo.toml
+index 1e83f05e0ca..4d0172071c1 100644
+--- a/Cargo.toml
++++ b/Cargo.toml
+@@ -113,6 +113,8 @@ cargo-util = { path = "src/tools/cargo/crates/cargo-util" }
+ [patch.crates-io]
++libc = { git = "https://github.com/rust-lang/libc", rev = "0bf7ce340699dcbacabdf5f16a242d2219a49ee0" }
+
+ # See comments in `src/tools/rustc-workspace-hack/README.md` for what's going on
+ # here
+ rustc-workspace-hack = { path = 'src/tools/rustc-workspace-hack' }
+
+After this, run cargo update -p libc
to update the lockfiles.
Beware that if you patch to a local path
dependency, this will enable
+warnings for that dependency. Some dependencies are not warning-free, and due
+to the deny-warnings
setting in config.toml
, the build may suddenly start
+to fail.
+To work around warnings, you may want to:
# config.toml
+[rust]
+deny-warnings = false
+
+Once you have a target specification in JSON and in the code, you can
+cross-compile rustc
:
DESTDIR=/path/to/install/in \
+./x install -i --stage 1 --host aarch64-apple-darwin.json --target aarch64-apple-darwin \
+compiler/rustc library/std
+
+If your target specification is already available in the bootstrap +compiler, you can use it instead of the JSON file for both arguments.
+There are two levels of tier 2 targets:
+a) Targets that are only cross-compiled (rustup target add
)
+b) Targets that have a native toolchain (rustup toolchain install
)
For an example of promoting a target from cross-compiled to native, +see #75914.
+ +There are multiple additional build configuration options and techniques that can be used to compile a
+build of rustc
that is as optimized as possible (for example when building rustc
for a Linux
+distribution). The status of these configuration options for various Rust targets is tracked here.
+This page describes how you can use these approaches when building rustc
yourself.
Link-time optimization is a powerful compiler technique that can increase program performance. To
+enable (Thin-)LTO when building rustc
, set the rust.lto
config option to "thin"
+in config.toml
:
[rust]
+lto = "thin"
+
+++Note that LTO for
+rustc
is currently supported and tested only for +thex86_64-unknown-linux-gnu
target. Other targets may work, but no guarantees are provided. +Notably, LTO-optimizedrustc
currently produces miscompilations on Windows.
Enabling LTO on Linux has produced speed-ups by up to 10%.
+Using a different memory allocator for rustc
can provide significant performance benefits. If you
+want to enable the jemalloc
allocator, you can set the rust.jemalloc
option to true
+in config.toml
:
[rust]
+jemalloc = true
+
+++Note that this option is currently only supported for Linux and macOS targets.
+
Reducing the amount of codegen units per rustc
crate can produce a faster build of the compiler.
+You can modify the number of codegen units for rustc
and libstd
in config.toml
with the
+following options:
[rust]
+codegen-units = 1
+codegen-units-std = 1
+
+By default, rustc
is compiled for a generic (and conservative) instruction set architecture
+(depending on the selected target), to make it support as many CPUs as possible. If you want to
+compile rustc
for a specific instruction set architecture, you can set the target_cpu
compiler
+option in RUSTFLAGS
:
RUSTFLAGS="-C target_cpu=x86-64-v3" ./x build ...
+
+If you also want to compile LLVM for a specific instruction set, you can set llvm
flags
+in config.toml
:
[llvm]
+cxxflags = "-march=x86-64-v3"
+cflags = "-march=x86-64-v3"
+
+Applying profile-guided optimizations (or more generally, feedback-directed optimizations) can
+produce a large increase to rustc
performance, by up to 15% (1, 2). However, these techniques
+are not simply enabled by a configuration option, but rather they require a complex build workflow
+that compiles rustc
multiple times and profiles it on selected benchmarks.
There is a tool called opt-dist
that is used to optimize rustc
with PGO (profile-guided
+optimizations) and BOLT (a post-link binary optimizer) for builds distributed to end users. You
+can examine the tool, which is located in src/tools/opt-dist
, and build a custom PGO build
+workflow based on it, or try to use it directly. Note that the tool is currently quite hardcoded to
+the way we use it in Rust's continuous integration workflows, and it might require some custom
+changes to make it work in a different environment.
To use the tool, you will need to provide some external dependencies:
+x.py
).llvm-profdata
binary. Optionally, if you want to use BOLT,
+the llvm-bolt
and
+merge-fdata
binaries have to be available in the toolchain.These dependencies are provided to opt-dist
by an implementation of the Environment
struct.
+It specifies directories where will the PGO/BOLT pipeline take place, and also external dependencies
+like Python or LLVM.
Here is an example of how can opt-dist
be used locally (outside of CI):
./x build tools/opt-dist
+
+local
mode and provide necessary parameters:
+./build/host/stage0-tools-bin/opt-dist local \
+ --target-triple <target> \ # select target, e.g. "x86_64-unknown-linux-gnu"
+ --checkout-dir <path> \ # path to rust checkout, e.g. "."
+ --llvm-dir <path> \ # path to built LLVM toolchain, e.g. "/foo/bar/llvm/install"
+ -- python3 x.py dist # pass the actual build command
+
+You can run --help
to see further parameters that you can modify.++ +Note: if you want to run the actual CI pipeline, instead of running
+opt-dist
locally, +you can executeDEPLOY=1 src/ci/docker/run.sh dist-x86_64-linux
.
See the rust-lang/rust
INSTALL.
You will need an internet connection to build. The bootstrapping process +involves updating git submodules and downloading a beta compiler. It doesn't +need to be super fast, but that can help.
+There are no strict hardware requirements, but building the compiler is +computationally expensive, so a beefier machine will help, and I wouldn't +recommend trying to build on a Raspberry Pi! We recommend the following.
+Beefier machines will lead to much faster builds. If your machine is not very
+powerful, a common strategy is to only use ./x check
on your local machine
+and let the CI build test your changes when you push to a PR branch.
Building the compiler takes more than half an hour on my moderately powerful +laptop. We suggest downloading LLVM from CI so you don't have to build it from source +(see here).
+Like cargo
, the build system will use as many cores as possible. Sometimes
+this can cause you to run low on memory. You can use -j
to adjust the number
+of concurrent jobs. If a full build takes more than ~45 minutes to an hour, you
+are probably spending most of the time swapping memory in and out; try using
+-j1
.
If you don't have too much free disk space, you may want to turn off +incremental compilation (see here). This will make compilation take +longer (especially after a rebase), but will save a ton of space from the +incremental caches.
+ +This is a quickstart guide about getting the compiler running. For more +information on the individual steps, see the other pages in this chapter.
+First, clone the repository:
+git clone https://github.com/rust-lang/rust.git
+cd rust
+
+When building the compiler, we don't use cargo
directly, instead we use a
+wrapper called "x". It is invoked with ./x
.
We need to create a configuration for the build. Use ./x setup
to create a
+good default.
./x setup
+
+Then, we can build the compiler. Use ./x build
to build the compiler, standard
+library and a few tools. You can also ./x check
to just check it. All these
+commands can take specific components/paths as arguments, for example ./x check compiler
to just check the compiler.
./x build
+
+++When doing a change to the compiler that does not affect the way it compiles +the standard library (so for example, a change to an error message), use +
+--keep-stage-std 1
to avoid recompiling it.
After building the compiler and standard library, you now have a working +compiler toolchain. You can use it with rustup by linking it.
+rustup toolchain link stage1 build/host/stage1
+
+Now you have a toolchain called stage1
linked to your build. You can use it to
+test the compiler.
rustc +stage1 testfile.rs
+
+After doing a change, you can run the compiler test suite with ./x test
.
./x test
runs the full test suite, which is slow and rarely what you want.
+Usually, ./x test tests/ui
is what you want after a compiler change, testing
+all UI tests that invoke the compiler on a specific test file
+and check the output.
./x test tests/ui
+
+Use --bless
if you've made a change and want to update the .stderr
files
+with the new output.
+++
./x suggest
can also be helpful for suggesting which tests to run after a +change.
Congrats, you are now ready to make a change to the compiler! If you have more +questions, the full chapter might contain the +answers, and if it doesn't, feel free to ask for help on +Zulip.
+If you use VSCode, Vim, Emacs or Helix, ./x setup
will ask you if you want to
+set up the editor config. For more information, check out suggested
+workflows.
The full bootstrapping process takes quite a while. Here are some suggestions to +make your life easier.
+rust-analyzer
for rustc
+
+x suggest
rustup
to use nightly--keep-stage
.CI will automatically fail your build if it doesn't pass tidy
, our internal
+tool for ensuring code quality. If you'd like, you can install a Git
+hook that will
+automatically run ./x test tidy
on each push, to ensure your code is up to
+par. If the hook fails then run ./x test tidy --bless
and commit the changes.
+If you decide later that the pre-push behavior is undesirable, you can delete
+the pre-push
file in .git/hooks
.
A prebuilt git hook lives at src/etc/pre-push.sh
. It can be copied into
+your .git/hooks
folder as pre-push
(without the .sh
extension!).
You can also install the hook as a step of running ./x setup
!
rust-analyzer
for rustc
rust-analyzer
can help you check and format your code whenever you save a
+file. By default, rust-analyzer
runs the cargo check
and rustfmt
commands,
+but you can override these commands to use more adapted versions of these tools
+when hacking on rustc
. With custom setup, rust-analyzer
can use ./x check
+to check the sources, and the stage 0 rustfmt to format them.
The default rust-analyzer.check.overrideCommand
command line will check all
+the crates and tools in the repository. If you are working on a specific part,
+you can override the command to only check the part you are working on to save
+checking time. For example, if you are working on the compiler, you can override
+the command to x check compiler --json-output
to only check the compiler part.
+You can run x check --help --verbose
to see the available parts.
Running ./x setup editor
will prompt you to create a project-local LSP config
+file for one of the supported editors. You can also create the config file as a
+step of running ./x setup
.
By default, when rust-analyzer runs a check or format command, it will share +the same build directory as manual command-line builds. This can be inconvenient +for two reasons:
+To avoid these problems:
+--build-dir=build-rust-analyzer
to all of the custom x
commands in
+your editor's rust-analyzer configuration.
+(Feel free to choose a different directory name if desired.)rust-analyzer.rustfmt.overrideCommand
setting so that it points
+to the copy of rustfmt
in that other build directory.rust-analyzer.procMacro.server
setting so that it points to the
+copy of rust-analyzer-proc-macro-srv
in that other build directory.Using separate build directories for command-line builds and rust-analyzer
+requires extra disk space, and also means that running ./x clean
on the
+command-line will not clean out the separate build directory. To clean the
+separate build directory, run ./x clean --build-dir=build-rust-analyzer
+instead.
Selecting vscode
in ./x setup editor
will prompt you to create a
+.vscode/settings.json
file which will configure Visual Studio code. The
+recommended rust-analyzer
settings live at
+src/etc/rust_analyzer_settings.json
.
If running ./x check
on save is inconvenient, in VS Code you can use a Build
+Task instead:
// .vscode/tasks.json
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "./x check",
+ "command": "./x check",
+ "type": "shell",
+ "problemMatcher": "$rustc",
+ "presentation": { "clear": true },
+ "group": { "kind": "build", "isDefault": true }
+ }
+ ]
+}
+
+For Neovim users there are several options for configuring for rustc. The +easiest way is by using neoconf.nvim, +which allows for project-local configuration files with the native LSP. The +steps for how to use it are below. Note that they require rust-analyzer to +already be configured with Neovim. Steps for this can be found +here.
+./x setup editor
, and select vscode
to create a
+.vscode/settings.json
file. neoconf
is able to read and update
+rust-analyzer settings automatically when the project is opened when this
+file is detected.If you're using coc.nvim
, you can run ./x setup editor
and select vim
to
+create a .vim/coc-settings.json
. The settings can be edited with
+:CocLocalConfig
. The recommended settings live at
+src/etc/rust_analyzer_settings.json
.
Another way is without a plugin, and creating your own logic in your
+configuration. To do this you must translate the JSON to Lua yourself. The
+translation is 1:1 and fairly straight-forward. It must be put in the
+["rust-analyzer"]
key of the setup table, which is shown
+here.
If you would like to use the build task that is described above, you may either
+make your own command in your config, or you can install a plugin such as
+overseer.nvim that can read
+VSCode's task.json
+files,
+and follow the same instructions as above.
Emacs provides support for rust-analyzer with project-local configuration
+through Eglot.
+Steps for setting up Eglot with rust-analyzer can be found
+here.
+Having set up Emacs & Eglot for Rust development in general, you can run
+./x setup editor
and select emacs
, which will prompt you to create
+.dir-locals.el
with the recommended configuration for Eglot.
+The recommended settings live at src/etc/rust_analyzer_eglot.el
.
+For more information on project-specific Eglot configuration, consult the
+manual.
Helix comes with built-in LSP and rust-analyzer support.
+It can be configured through languages.toml
, as described
+here.
+You can run ./x setup editor
and select helix
, which will prompt you to
+create languages.toml
with the recommended configuration for Helix. The
+recommended settings live at src/etc/rust_analyzer_helix.toml
.
When doing simple refactoring, it can be useful to run ./x check
+continuously. If you set up rust-analyzer
as described above, this will be
+done for you every time you save a file. Here you are just checking that the
+compiler can build, but often that is all you need (e.g., when renaming a
+method). You can then run ./x build
when you actually need to run tests.
In fact, it is sometimes useful to put off tests even when you are not 100% sure
+the code will work. You can then keep building up refactoring commits and only
+run the tests at some later time. You can then use git bisect
to track down
+precisely which commit caused the problem. A nice side-effect of this style
+is that you are left with a fairly fine-grained set of commits at the end, all
+of which build and pass tests. This often helps reviewing.
x suggest
The x suggest
subcommand suggests (and runs) a subset of the extensive
+rust-lang/rust
tests based on files you have changed. This is especially
+useful for new contributors who have not mastered the arcane x
flags yet and
+more experienced contributors as a shorthand for reducing mental effort. In all
+cases it is useful not to run the full tests (which can take on the order of
+tens of minutes) and just run a subset which are relevant to your changes. For
+example, running tidy
and linkchecker
is useful when editing Markdown files,
+whereas UI tests are much less likely to be helpful. While x suggest
is a
+useful tool, it does not guarantee perfect coverage (just as PR CI isn't a
+substitute for bors). See the dedicated chapter for
+more information and contribution instructions.
Please note that x suggest
is in a beta state currently and the tests that it
+will suggest are limited.
rustup
to use nightlySome parts of the bootstrap process uses pinned, nightly versions of tools like
+rustfmt. To make things like cargo fmt
work correctly in your repo, run
cd <path to rustc repo>
+rustup override set nightly
+
+after installing a nightly toolchain with rustup
. Don't forget to do this
+for all directories you have setup a worktree for. You may need to use the
+pinned nightly version from src/stage0
, but often the normal nightly
channel
+will work.
Note see the section on vscode for how to configure it with this real
+rustfmt x
uses, and the section on rustup for how to setup rustup
+toolchain for your bootstrapped compiler
Note This does not allow you to build rustc
with cargo directly. You
+still have to use x
to work on the compiler or standard library, this just
+lets you use cargo fmt
.
--keep-stage
.Sometimes just checking whether the compiler builds is not enough. A common
+example is that you need to add a debug!
statement to inspect the value of
+some state or better understand the problem. In that case, you don't really need
+a full build. By bypassing bootstrap's cache invalidation, you can often get
+these builds to complete very fast (e.g., around 30 seconds). The only catch is
+this requires a bit of fudging and may produce compilers that don't work (but
+that is easily detected and fixed).
The sequence of commands you want is as follows:
+./x build library
+std
+compatible with the stage1 compiler) as well as the first few steps of the
+"stage 1 actions" up to "stage1 (sysroot stage1) builds std"../x build library --keep-stage 1
+--keep-stage 1
flag hereAs mentioned, the effect of --keep-stage 1
is that we just assume that the
+old standard library can be re-used. If you are editing the compiler, this is
+almost always true: you haven't changed the standard library, after all. But
+sometimes, it's not true: for example, if you are editing the "metadata" part of
+the compiler, which controls how the compiler encodes types and other states
+into the rlib
files, or if you are editing things that wind up in the metadata
+(such as the definition of the MIR).
The TL;DR is that you might get weird behavior from a compile when using
+--keep-stage 1
-- for example, strange ICEs
+or other panics. In that case, you should simply remove the --keep-stage 1
+from the command and rebuild. That ought to fix the problem.
You can also use --keep-stage 1
when running tests. Something like this:
./x test tests/ui
./x test tests/ui --keep-stage 1
You can further enable the --incremental
flag to save additional time in
+subsequent rebuilds:
./x test tests/ui --incremental --test-args issue-1234
+
+If you don't want to include the flag with every command, you can enable it in
+the config.toml
:
[rust]
+incremental = true
+
+Note that incremental compilation will use more disk space than usual. If disk
+space is a concern for you, you might want to check the size of the build
+directory from time to time.
Setting optimize = false
makes the compiler too slow for tests. However, to
+improve the test cycle, you can disable optimizations selectively only for the
+crates you'll have to rebuild
+(source).
+For example, when working on rustc_mir_build
, the rustc_mir_build
and
+rustc_driver
crates take the most time to incrementally rebuild. You could
+therefore set the following in the root Cargo.toml
:
[profile.release.package.rustc_mir_build]
+opt-level = 0
+[profile.release.package.rustc_driver]
+opt-level = 0
+
+Working on multiple branches in parallel can be a little annoying, since +building the compiler on one branch will cause the old build and the incremental +compilation cache to be overwritten. One solution would be to have multiple +clones of the repository, but that would mean storing the Git metadata multiple +times, and having to update each clone individually.
+Fortunately, Git has a better solution called worktrees. This lets you create +multiple "working trees", which all share the same Git database. Moreover, +because all of the worktrees share the same object database, if you update a +branch (e.g. master) in any of them, you can use the new commits from any of the +worktrees. One caveat, though, is that submodules do not get shared. They will +still be cloned multiple times.
+Given you are inside the root directory for your Rust repository, you can create +a "linked working tree" in a new "rust2" directory by running the following +command:
+git worktree add ../rust2
+
+Creating a new worktree for a new branch based on master
looks like:
git worktree add -b my-feature ../rust2 master
+
+You can then use that rust2 folder as a separate workspace for modifying and
+building rustc
!
If you're using nix, you can use the following nix-shell to work on Rust:
+{ pkgs ? import <nixpkgs> {} }:
+pkgs.mkShell {
+ name = "rustc";
+ nativeBuildInputs = with pkgs; [
+ binutils cmake ninja pkg-config python3 git curl cacert patchelf nix
+ ];
+ buildInputs = with pkgs; [
+ openssl glibc.out glibc.static
+ ];
+ # Avoid creating text files for ICEs.
+ RUSTC_ICE = "0";
+ # Provide `libstdc++.so.6` for the self-contained lld.
+ LD_LIBRARY_PATH = "${with pkgs; lib.makeLibraryPath [
+ stdenv.cc.cc.lib
+ ]}";
+}
+
+Note that when using nix on a not-NixOS distribution, it may be necessary to set
+patch-binaries-for-nix = true
in config.toml
. Bootstrap tries to detect
+whether it's running in nix and enable patching automatically, but this
+detection can have false negatives.
You can also use your nix shell to manage config.toml
:
let
+ config = pkgs.writeText "rustc-config" ''
+ # Your config.toml content goes here
+ ''
+pkgs.mkShell {
+ /* ... */
+ # This environment variable tells bootstrap where our config.toml is.
+ RUST_BOOTSTRAP_CONFIG = config;
+}
+
+If you use Bash, Fish or PowerShell, you can find automatically-generated shell
+completion scripts for x.py
in
+src/etc/completions
.
+Zsh support will also be included once issues with
+clap_complete
have been resolved.
You can use source ./src/etc/completions/x.py.<extension>
to load completions
+for your shell of choice, or & .\src\etc\completions\x.py.ps1
for PowerShell.
+Adding this to your shell's startup script (e.g. .bashrc
) will automatically
+load this completion.
Command-line flags are documented in the rustc book. All stable +flags should be documented there. Unstable flags should be documented in the +unstable book.
+See the forge guide for new options for details on the procedure for +adding a new command-line argument.
+foo
and bar
, an additional
+--json
flag is better than adding --foo-json
and --bar-json
.no-
prefix. Instead, use the parse_bool
function,
+such as -C embed-bitcode=no
.-o
) should generate an error
+if it is too ambiguous what multiple flags would mean.--verbose
flag is for adding verbose information to rustc
+output. For example, using it with the --version
+flag gives information about the hashes of the compiler code.-Z unstable-options
flag.This section describes how rustc handles closures. Closures in Rust are
+effectively "desugared" into structs that contain the values they use (or
+references to the values they use) from their creator's stack frame. rustc has
+the job of figuring out which values a closure uses and how, so it can decide
+whether to capture a given variable by shared reference, mutable reference, or
+by move. rustc also has to figure out which of the closure traits (Fn
,
+FnMut
, or FnOnce
) a closure is capable of
+implementing.
Let's start with a few examples:
+To start, let's take a look at how the closure in the following example is desugared:
++fn closure(f: impl Fn()) { + f(); +} + +fn main() { + let x: i32 = 10; + closure(|| println!("Hi {}", x)); // The closure just reads x. + println!("Value of x after return {}", x); +} +
Let's say the above is the content of a file called immut.rs
. If we compile
+immut.rs
using the following command. The -Z dump-mir=all
flag will cause
+rustc
to generate and dump the MIR to a directory called mir_dump
.
> rustc +stage1 immut.rs -Z dump-mir=all
+
+After we run this command, we will see a newly generated directory in our
+current working directory called mir_dump
, which will contain several files.
+If we look at file rustc.main.-------.mir_map.0.mir
, we will find, among
+other things, it also contains this line:
_4 = &_1;
+_3 = [closure@immut.rs:7:13: 7:36] { x: move _4 };
+
+Note that in the MIR examples in this chapter, _1
is x
.
Here in first line _4 = &_1;
, the mir_dump
tells us that x
was borrowed
+as an immutable reference. This is what we would hope as our closure just
+reads x
.
Here is another example:
++fn closure(mut f: impl FnMut()) { + f(); +} + +fn main() { + let mut x: i32 = 10; + closure(|| { + x += 10; // The closure mutates the value of x + println!("Hi {}", x) + }); + println!("Value of x after return {}", x); +} +
_4 = &mut _1;
+_3 = [closure@mut.rs:7:13: 10:6] { x: move _4 };
+
+This time along, in the line _4 = &mut _1;
, we see that the borrow is changed to mutable borrow.
+Fair enough! The closure increments x
by 10.
One more example:
++fn closure(f: impl FnOnce()) { + f(); +} + +fn main() { + let x = vec![21]; + closure(|| { + drop(x); // Makes x unusable after the fact. + }); + // println!("Value of x after return {:?}", x); +} +
_6 = [closure@move.rs:7:13: 9:6] { x: move _1 }; // bb16[3]: scope 1 at move.rs:7:13: 9:6
+
+Here, x
is directly moved into the closure and the access to it will not be permitted after the
+closure.
Now let's dive into rustc code and see how all these inferences are done by the compiler.
+Let's start with defining a term that we will be using quite a bit in the rest of the discussion -
+upvar. An upvar is a variable that is local to the function where the closure is defined. So,
+in the above examples, x will be an upvar to the closure. They are also sometimes referred to as
+the free variables meaning they are not bound to the context of the closure.
+compiler/rustc_passes/src/upvars.rs
defines a query called upvars_mentioned
+for this purpose.
Other than lazy invocation, one other thing that distinguishes a closure from a
+normal function is that it can use the upvars. It borrows these upvars from its surrounding
+context; therefore the compiler has to determine the upvar's borrow type. The compiler starts with
+assigning an immutable borrow type and lowers the restriction (that is, changes it from
+immutable to mutable to move) as needed, based on the usage. In the Example 1 above, the
+closure only uses the variable for printing but does not modify it in any way and therefore, in the
+mir_dump
, we find the borrow type for the upvar x
to be immutable. In example 2, however, the
+closure modifies x
and increments it by some value. Because of this mutation, the compiler, which
+started off assigning x
as an immutable reference type, has to adjust it as a mutable reference.
+Likewise in the third example, the closure drops the vector and therefore this requires the variable
+x
to be moved into the closure. Depending on the borrow kind, the closure has to implement the
+appropriate trait: Fn
trait for immutable borrow, FnMut
for mutable borrow,
+and FnOnce
for move semantics.
Most of the code related to the closure is in the
+compiler/rustc_hir_typeck/src/upvar.rs
file and the data structures are
+declared in the file compiler/rustc_middle/src/ty/mod.rs
.
Before we go any further, let's discuss how we can examine the flow of control through the rustc
+codebase. For closures specifically, set the RUSTC_LOG
env variable as below and collect the
+output in a file:
> RUSTC_LOG=rustc_hir_typeck::upvar rustc +stage1 -Z dump-mir=all \
+ <.rs file to compile> 2> <file where the output will be dumped>
+
+This uses the stage1 compiler and enables debug!
logging for the
+rustc_hir_typeck::upvar
module.
The other option is to step through the code using lldb or gdb.
+rust-lldb build/host/stage1/bin/rustc test.rs
b upvar.rs:134
// Setting the breakpoint on a certain line in the upvar.rs file`r
// Run the program until it hits the breakpointLet's start with upvar.rs
. This file has something called
+the euv::ExprUseVisitor
which walks the source of the closure and
+invokes a callback for each upvar that is borrowed, mutated, or moved.
+fn main() { + let mut x = vec![21]; + let _cl = || { + let y = x[0]; // 1. + x[0] += 1; // 2. + }; +} +
In the above example, our visitor will be called twice, for the lines marked 1 and 2, once for a +shared borrow and another one for a mutable borrow. It will also tell us what was borrowed.
+The callbacks are defined by implementing the Delegate
trait. The
+InferBorrowKind
type implements Delegate
and keeps a map that
+records for each upvar which mode of capture was required. The modes of capture
+can be ByValue
(moved) or ByRef
(borrowed). For ByRef
borrows, the possible
+BorrowKind
s are ImmBorrow
, UniqueImmBorrow
, MutBorrow
as defined in the
+compiler/rustc_middle/src/ty/mod.rs
.
Delegate
defines a few different methods (the different callbacks):
+consume for move of a variable, borrow for a borrow of some kind
+(shared or mutable), and mutate when we see an assignment of something.
All of these callbacks have a common argument cmt which stands for Category,
+Mutability and Type and is defined in
+compiler/rustc_hir_typeck/src/expr_use_visitor.rs
. Borrowing from the code
+comments, "cmt
is a complete categorization of a value indicating where it
+originated and how it is located, as well as the mutability of the memory in
+which the value is stored". Based on the callback (consume, borrow etc.), we
+will call the relevant adjust_upvar_borrow_kind_for_<something>
and pass the
+cmt
along. Once the borrow type is adjusted, we store it in the table, which
+basically says what borrows were made for each closure.
self.tables
+ .borrow_mut()
+ .upvar_capture_map
+ .extend(delegate.adjust_upvar_captures);
+
+
+ ++NOTE: this is based on notes by @lcnr
+
Coherence checking is what detects both of trait impls and inherent impls overlapping with others.
+(reminder: inherent impls are impls of concrete types like impl MyStruct {}
)
Overlapping trait impls always produce an error, +while overlapping inherent impls result in an error only if they have methods with the same name.
+Checking for overlaps is split in two parts. First there's the overlap check(s), +which finds overlaps between traits and inherent implementations that the compiler currently knows about.
+However, Coherence also results in an error if any other impls could exist, +even if they are currently unknown. +This affects impls which may get added to upstream crates in a backwards compatible way, +and impls from downstream crates. +This is called the Orphan check.
+Overlap checks are performed for both inherent impls, and for trait impls. +This uses the same overlap checking code, really done as two separate analyses. +Overlap checks always consider pairs of implementations, comparing them to each other.
+Overlap checking for inherent impl blocks is done through fn check_item
in coherence/inherent_impls_overlap.rs),
+where you can very clearly see that (at least for small n
), the check really performs n^2
+comparisons between impls.
In the case of traits, this check is currently done as part of building the specialization graph, +to handle specializing impls overlapping with their parent, but this may change in the future.
+In both cases, all pairs of impls are checked for overlap.
+Overlapping is sometimes partially allowed:
+but normally isn't.
+The overlap check has various modes (see OverlapMode
).
+Importantly, there's the explicit negative impl check, and the implicit negative impl check.
+Both try to prove that an overlap is definitely impossible.
This check is done in impl_intersection_has_negative_obligation
.
This check tries to find a negative trait implementation. +For example:
++#![allow(unused)] +fn main() { +struct MyCustomErrorType; + +// both in your own crate +impl From<&str> for MyCustomErrorType {} +impl<E> From<E> for MyCustomErrorType where E: Error {} +} +
In this example, we'd get:
+MyCustomErrorType: From<&str>
and MyCustomErrorType: From<?E>
, giving ?E = &str
.
And thus, these two implementations would overlap.
+However, libstd provides &str: !Error
, and therefore guarantees that there
+will never be a positive implementation of &str: Error
, and thus there is no overlap.
Note that for this kind of negative impl check, we must have explicit negative implementations provided. +This is not currently stable.
+This check is done in impl_intersection_has_impossible_obligation
,
+and does not rely on negative trait implementations and is stable.
Let's say there's a
++#![allow(unused)] +fn main() { +impl From<MyLocalType> for Box<dyn Error> {} // in your own crate +impl<E> From<E> for Box<dyn Error> where E: Error {} // in std +} +
This would give: Box<dyn Error>: From<MyLocalType>
, and Box<dyn Error>: From<?E>
,
+giving ?E = MyLocalType
.
In your crate there's no MyLocalType: Error
, downstream crates cannot implement Error
(a remote trait) for MyLocalType
(a remote type).
+Therefore, these two impls do not overlap.
+Importantly, this works even if there isn't a impl !Error for MyLocalType
.
-Z
flags
+
+#[rustc_*]
TEST attributes
+
+rustc
This chapter contains a few tips to debug the compiler. These tips aim to be +useful no matter what you are working on. Some of the other chapters have +advice about specific parts of the compiler (e.g. the Queries Debugging and +Testing chapter or the LLVM Debugging +chapter).
+By default, rustc is built without most debug information. To enable debug info,
+set debug = true
in your config.toml.
Setting debug = true
turns on many different debug options (e.g., debug-assertions
,
+debug-logging
, etc.) which can be individually tweaked if you want to, but many people
+simply set debug = true
.
If you want to use GDB to debug rustc, please set config.toml
with options:
[rust]
+debug = true
+debuginfo-level = 2
+
+++NOTE: +This will use a lot of disk space +(upwards of 35GB), +and will take a lot more compile time. +With
+debuginfo-level = 1
(the default whendebug = true
), +you will be able to track the execution path, +but will lose the symbol information for debugging.
The default configuration will enable symbol-mangling-version
v0.
+This requires at least GDB v10.2,
+otherwise you need to disable new symbol-mangling-version in config.toml
.
[rust]
+new-symbol-mangling = false
+
+++See the comments in
+config.example.toml
for more info.
You will need to rebuild the compiler after changing any configuration option.
+By default, if rustc encounters an Internal Compiler Error (ICE) it will dump the ICE contents to an
+ICE file within the current working directory named rustc-ice-<timestamp>-<pid>.txt
. If this is
+not desirable, you can prevent the ICE file from being created with RUSTC_ICE=0
.
When you have an ICE (panic in the compiler), you can set
+RUST_BACKTRACE=1
to get the stack trace of the panic!
like in
+normal Rust programs. IIRC backtraces don't work on MinGW,
+sorry. If you have trouble or the backtraces are full of unknown
,
+you might want to find some way to use Linux, Mac, or MSVC on Windows.
In the default configuration (without debug
set to true
), you don't have line numbers
+enabled, so the backtrace looks like this:
stack backtrace:
+ 0: std::sys::imp::backtrace::tracing::imp::unwind_backtrace
+ 1: std::sys_common::backtrace::_print
+ 2: std::panicking::default_hook::{{closure}}
+ 3: std::panicking::default_hook
+ 4: std::panicking::rust_panic_with_hook
+ 5: std::panicking::begin_panic
+ (~~~~ LINES REMOVED BY ME FOR BREVITY ~~~~)
+ 32: rustc_typeck::check_crate
+ 33: <std::thread::local::LocalKey<T>>::with
+ 34: <std::thread::local::LocalKey<T>>::with
+ 35: rustc::ty::context::TyCtxt::create_and_enter
+ 36: rustc_driver::driver::compile_input
+ 37: rustc_driver::run_compiler
+
+If you set debug = true
, you will get line numbers for the stack trace.
+Then the backtrace will look like this:
stack backtrace:
+ (~~~~ LINES REMOVED BY ME FOR BREVITY ~~~~)
+ at /home/user/rust/compiler/rustc_typeck/src/check/cast.rs:110
+ 7: rustc_typeck::check::cast::CastCheck::check
+ at /home/user/rust/compiler/rustc_typeck/src/check/cast.rs:572
+ at /home/user/rust/compiler/rustc_typeck/src/check/cast.rs:460
+ at /home/user/rust/compiler/rustc_typeck/src/check/cast.rs:370
+ (~~~~ LINES REMOVED BY ME FOR BREVITY ~~~~)
+ 33: rustc_driver::driver::compile_input
+ at /home/user/rust/compiler/rustc_driver/src/driver.rs:1010
+ at /home/user/rust/compiler/rustc_driver/src/driver.rs:212
+ 34: rustc_driver::run_compiler
+ at /home/user/rust/compiler/rustc_driver/src/lib.rs:253
+
+-Z
flagsThe compiler has a bunch of -Z *
flags. These are unstable flags that are only
+enabled on nightly. Many of them are useful for debugging. To get a full listing
+of -Z
flags, use -Z help
.
One useful flag is -Z verbose-internals
, which generally enables printing more
+info that could be useful for debugging.
Right below you can find elaborate explainers on a selected few.
+If you want to get a backtrace to the point where the compiler emits an
+error message, you can pass the -Z treat-err-as-bug=n
, which will make
+the compiler panic on the nth
error. If you leave off =n
, the compiler will
+assume 1
for n
and thus panic on the first error it encounters.
For example:
+$ cat error.rs
+
++fn main() { + 1 + (); +} +
$ rustc +stage1 error.rs
+error[E0277]: cannot add `()` to `{integer}`
+ --> error.rs:2:7
+ |
+2 | 1 + ();
+ | ^ no implementation for `{integer} + ()`
+ |
+ = help: the trait `Add<()>` is not implemented for `{integer}`
+
+error: aborting due to previous error
+
+Now, where does the error above come from?
+$ RUST_BACKTRACE=1 rustc +stage1 error.rs -Z treat-err-as-bug
+error[E0277]: the trait bound `{integer}: std::ops::Add<()>` is not satisfied
+ --> error.rs:2:7
+ |
+2 | 1 + ();
+ | ^ no implementation for `{integer} + ()`
+ |
+ = help: the trait `std::ops::Add<()>` is not implemented for `{integer}`
+
+error: internal compiler error: unexpected panic
+
+note: the compiler unexpectedly panicked. this is a bug.
+
+note: we would appreciate a bug report: https://github.com/rust-lang/rust/blob/master/CONTRIBUTING.md#bug-reports
+
+note: rustc 1.24.0-dev running on x86_64-unknown-linux-gnu
+
+note: run with `RUST_BACKTRACE=1` for a backtrace
+
+thread 'rustc' panicked at 'encountered error with `-Z treat_err_as_bug',
+/home/user/rust/compiler/rustc_errors/src/lib.rs:411:12
+note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose
+backtrace.
+stack backtrace:
+ (~~~ IRRELEVANT PART OF BACKTRACE REMOVED BY ME ~~~)
+ 7: rustc::traits::error_reporting::<impl rustc::infer::InferCtxt<'a, 'tcx>>
+ ::report_selection_error
+ at /home/user/rust/compiler/rustc_middle/src/traits/error_reporting.rs:823
+ 8: rustc::traits::error_reporting::<impl rustc::infer::InferCtxt<'a, 'tcx>>
+ ::report_fulfillment_errors
+ at /home/user/rust/compiler/rustc_middle/src/traits/error_reporting.rs:160
+ at /home/user/rust/compiler/rustc_middle/src/traits/error_reporting.rs:112
+ 9: rustc_typeck::check::FnCtxt::select_obligations_where_possible
+ at /home/user/rust/compiler/rustc_typeck/src/check/mod.rs:2192
+ (~~~ IRRELEVANT PART OF BACKTRACE REMOVED BY ME ~~~)
+ 36: rustc_driver::run_compiler
+ at /home/user/rust/compiler/rustc_driver/src/lib.rs:253
+
+Cool, now I have a backtrace for the error!
+The -Z eagerly-emit-delayed-bugs
option makes it easy to debug delayed bugs.
+It turns them into normal errors, i.e. makes them visible. This can be used in
+combination with -Z treat-err-as-bug
to stop at a particular delayed bug and
+get a backtrace.
-Z track-diagnostics
can help figure out where errors are emitted. It uses #[track_caller]
+for this and prints its location alongside the error:
$ RUST_BACKTRACE=1 rustc +stage1 error.rs -Z track-diagnostics
+error[E0277]: cannot add `()` to `{integer}`
+ --> src\error.rs:2:7
+ |
+2 | 1 + ();
+ | ^ no implementation for `{integer} + ()`
+-Ztrack-diagnostics: created at compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs:638:39
+ |
+ = help: the trait `Add<()>` is not implemented for `{integer}`
+ = help: the following other types implement trait `Add<Rhs>`:
+ <&'a f32 as Add<f32>>
+ <&'a f64 as Add<f64>>
+ <&'a i128 as Add<i128>>
+ <&'a i16 as Add<i16>>
+ <&'a i32 as Add<i32>>
+ <&'a i64 as Add<i64>>
+ <&'a i8 as Add<i8>>
+ <&'a isize as Add<isize>>
+ and 48 others
+
+For more information about this error, try `rustc --explain E0277`.
+
+This is similar but different to -Z treat-err-as-bug
:
The compiler uses the tracing
crate for logging.
For details see the guide section on tracing
+The cargo-bisect-rustc tool can be used as a quick and easy way to
+find exactly which PR caused a change in rustc
behavior. It automatically
+downloads rustc
PR artifacts and tests them against a project you provide
+until it finds the regression. You can then look at the PR to get more context
+on why it was changed. See this tutorial on how to use
+it.
The rustup-toolchain-install-master tool by kennytm can be used to
+download the artifacts produced by Rust's CI for a specific SHA1 -- this
+basically corresponds to the successful landing of some PR -- and then sets
+them up for your local use. This also works for artifacts produced by @bors try
. This is helpful when you want to examine the resulting build of a PR
+without doing the build yourself.
#[rustc_*]
TEST attributesThe compiler defines a whole lot of internal (perma-unstable) attributes some of which are useful
+for debugging by dumping extra compiler-internal information. These are prefixed with rustc_
and
+are gated behind the internal feature rustc_attrs
(enabled via e.g. #![feature(rustc_attrs)]
).
For a complete and up to date list, see builtin_attrs
. More specifically, the ones marked TEST
.
+Here are some notable ones:
Attribute | Description |
---|---|
rustc_def_path | Dumps the def_path_str of an item. |
rustc_dump_def_parents | Dumps the chain of DefId parents of certain definitions. |
rustc_dump_item_bounds | Dumps the item_bounds of an item. |
rustc_dump_predicates | Dumps the predicates_of an item. |
rustc_dump_vtable | |
rustc_hidden_type_of_opaques | Dumps the hidden type of each opaque types in the crate. |
rustc_layout | See this section. |
rustc_object_lifetime_default | Dumps the object lifetime defaults of an item. |
rustc_outlives | Dumps implied bounds of an item. More precisely, the inferred_outlives_of an item. |
rustc_regions | Dumps NLL closure region requirements. |
rustc_symbol_name | Dumps the mangled & demangled symbol_name of an item. |
rustc_variances | Dumps the variances of an item. |
Right below you can find elaborate explainers on a selected few.
+Some compiler options for debugging specific features yield graphviz graphs -
+e.g. the #[rustc_mir(borrowck_graphviz_postflow="suffix.dot")]
attribute
+dumps various borrow-checker dataflow graphs.
These all produce .dot
files. To view these files, install graphviz (e.g.
+apt-get install graphviz
) and then run the following commands:
$ dot -T pdf maybe_init_suffix.dot > maybe_init_suffix.pdf
+$ firefox maybe_init_suffix.pdf # Or your favorite pdf viewer
+
+The internal attribute #[rustc_layout]
can be used to dump the Layout
of
+the type it is attached to. For example:
+#![allow(unused)] +#![feature(rustc_attrs)] + +fn main() { +#[rustc_layout(debug)] +type T<'a> = &'a u32; +} +
Will emit the following:
+error: layout_of(&'a u32) = Layout {
+ fields: Primitive,
+ variants: Single {
+ index: 0,
+ },
+ abi: Scalar(
+ Scalar {
+ value: Pointer,
+ valid_range: 1..=18446744073709551615,
+ },
+ ),
+ largest_niche: Some(
+ Niche {
+ offset: Size {
+ raw: 0,
+ },
+ scalar: Scalar {
+ value: Pointer,
+ valid_range: 1..=18446744073709551615,
+ },
+ },
+ ),
+ align: AbiAndPrefAlign {
+ abi: Align {
+ pow2: 3,
+ },
+ pref: Align {
+ pow2: 3,
+ },
+ },
+ size: Size {
+ raw: 8,
+ },
+}
+ --> src/lib.rs:4:1
+ |
+4 | type T<'a> = &'a u32;
+ | ^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to previous error
+
+rustc
If you are using VSCode, and have edited your config.toml
to request debugging
+level 1 or 2 for the parts of the code you're interested in, then you should be
+able to use the CodeLLDB extension in VSCode to debug it.
Here is a sample launch.json
file, being used to run a stage 1 compiler direct
+from the directory where it is built (does not have to be "installed"):
// .vscode/launch.json
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "lldb",
+ "request": "launch",
+ "name": "Launch",
+ "args": [], // array of string command-line arguments to pass to compiler
+ "program": "${workspaceFolder}/build/host/stage1/bin/rustc",
+ "windows": { // applicable if using windows
+ "program": "${workspaceFolder}/build/host/stage1/bin/rustc.exe"
+ },
+ "cwd": "${workspaceFolder}", // current working directory at program start
+ "stopOnEntry": false,
+ "sourceLanguages": ["rust"]
+ }
+ ]
+ }
+
+
+ Now that we have seen what the compiler does,
+let's take a look at the structure of the rust-lang/rust
repository,
+where the rustc source code lives.
++You may find it helpful to read the "Overview of the compiler" +chapter, which introduces how the compiler works, before this one.
+
The rust-lang/rust
repository consists of a single large cargo workspace
+containing the compiler, the standard libraries (core
, alloc
, std
,
+proc_macro
, etc
), and rustdoc
, along with the build system and a
+bunch of tools and submodules for building a full Rust distribution.
The repository consists of three main directories:
+compiler/
contains the source code for rustc
. It consists of many crates
+that together make up the compiler.
library/
contains the standard libraries (core
, alloc
, std
,
+proc_macro
, test
), as well as the Rust runtime (backtrace
, rtstartup
,
+lang_start
).
tests/
contains the compiler tests.
src/
contains the source code for rustdoc
, clippy
, cargo
, the build system,
+language docs, etc.
The compiler is implemented in the various compiler/
crates.
+The compiler/
crates all have names starting with rustc_*
. These are a
+collection of around 50 interdependent crates ranging in size from tiny to
+huge. There is also the rustc
crate which is the actual binary (i.e. the
+main
function); it doesn't actually do anything besides calling the
+rustc_driver
crate, which drives the various parts of compilation in other
+crates.
The dependency structure of these crates is complex, but roughly it is +something like this:
+rustc
(the binary) calls rustc_driver::main
.
+rustc_driver
depends on a lot of other crates, but the main one is
+rustc_interface
.
+rustc_interface
depends on most of the other compiler crates. It
+is a fairly generic interface for driving the whole compilation.
+rustc_*
crates depend on rustc_middle
,
+which defines a lot of central data structures in the compiler.
+rustc_middle
and most of the other crates depend on a
+handful of crates representing the early parts of the
+compiler (e.g. the parser), fundamental data structures (e.g.
+Span
), or error reporting: rustc_data_structures
,
+rustc_span
, rustc_errors
, etc.You can see the exact dependencies by reading the Cargo.toml
for the various
+crates, just like a normal Rust crate.
One final thing: src/llvm-project
is a submodule for our fork of LLVM.
+During bootstrapping, LLVM is built and the compiler/rustc_llvm
crate
+contains Rust wrappers around LLVM (which is written in C++), so that the
+compiler can interface with it.
Most of this book is about the compiler, so we won't have any further +explanation of these crates here.
+The dependency structure of the compiler is influenced by two main factors:
+At the very bottom of the dependency tree are a handful of crates that are used
+by the whole compiler (e.g. rustc_span
). The very early parts of the
+compilation process (e.g. parsing and the Abstract Syntax Tree (AST
))
+depend on only these.
After the AST
is constructed and other early analysis is done, the
+compiler's query system gets set up. The query system is set up in a
+clever way using function pointers. This allows us to break dependencies
+between crates, allowing more parallel compilation. The query system is defined
+in rustc_middle
, so nearly all subsequent parts of the compiler depend on
+this crate. It is a really large crate, leading to long compile times. Some
+efforts have been made to move stuff out of it with varying success. Another
+side-effect is that sometimes related functionality gets scattered across
+different crates. For example, linting functionality is found across earlier
+parts of the crate, rustc_lint
, rustc_middle
, and other places.
Ideally there would be fewer, more cohesive crates, with incremental and +parallel compilation making sure compile times stay reasonable. However, +incremental and parallel compilation haven't gotten good enough for that yet, +so breaking things into separate crates has been our solution so far.
+At the top of the dependency tree is rustc_driver
and rustc_interface
+which is an unstable wrapper around the query system helping drive various
+stages of compilation. Other consumers of the compiler may use this interface
+in different ways (e.g. rustdoc
or maybe eventually rust-analyzer
). The
+rustc_driver
crate first parses command line arguments and then uses
+rustc_interface
to drive the compilation to completion.
The bulk of rustdoc
is in librustdoc
. However, the rustdoc
binary
+itself is src/tools/rustdoc
, which does nothing except call rustdoc::main
.
There is also JavaScript
and CSS
for the docs in src/tools/rustdoc-js
+and src/tools/rustdoc-themes
.
You can read more about rustdoc
in this chapter.
The test suite for all of the above is in tests/
. You can read more
+about the test suite in this chapter.
The test harness is in src/tools/compiletest/
.
There are a number of tools in the repository just for building the compiler,
+standard library, rustdoc
, etc, along with testing, building a full Rust
+distribution, etc.
One of the primary tools is src/bootstrap/
. You can read more about
+bootstrapping in this chapter. The process may also use other tools
+from src/tools/
, such as tidy/
or compiletest/
.
This code is fairly similar to most other Rust crates except that it must be
+built in a special way because it can use unstable (nightly
) features.
+The standard library is sometimes referred to as libstd or the "standard facade"
.
There are a lot of other things in the rust-lang/rust
repo that are related
+to building a full Rust distribution. Most of the time you don't need to worry about them.
These include:
+ + +rustc is maintained by the Rust compiler team. The people who belong to +this team collectively work to track regressions and implement new features. +Members of the Rust compiler team are people who have made significant +contributions to rustc and its design.
+Currently the compiler team chats in Zulip:
+t-compiler
stream on the Zulip instancet-compiler/help
, where people can ask for help
+with rustc development, or t-compiler/meetings
,
+where the team holds their weekly triage and steering meetings.If you're interested in figuring out who can answer questions about a +particular part of the compiler, or you'd just like to know who works on what, +check out triagebot.toml's assign section. +It contains a listing of the various parts of the compiler and a list of people +who are reviewers of each part.
+The compiler team has a weekly meeting where we do triage and try to +generally stay on top of new bugs, regressions, and discuss important +things in general. +They are held on Zulip. It works roughly as follows:
+The meeting currently takes place on Thursdays at 10am Boston time +(UTC-4 typically, but daylight savings time sometimes makes things +complicated).
+Membership in the Rust team is typically offered when someone has been +making significant contributions to the compiler for some +time. Membership is both a recognition but also an obligation: +compiler team members are generally expected to help with upkeep as +well as doing reviews and other work.
+If you are interested in becoming a compiler team member, the first +thing to do is to start fixing some bugs, or get involved in a working +group. One good way to find bugs is to look for +open issues tagged with E-easy +or +E-mentor.
+You can also dig through the graveyard of PRs that were +closed due to inactivity, +some of them may contain work that is still useful - refer to the +associated issues, if any - and only needs some finishing touches +for which the original author didn't have time.
+Once you have made a number of individual PRs to rustc, we will often +offer r+ privileges. This means that you have the right to instruct +"bors" (the robot that manages which PRs get landed into rustc) to +merge a PR +(here are some instructions for how to talk to bors).
+The guidelines for reviewers are as follows:
+Once you have r+ rights, you can also be added to the reviewer rotation.
+triagebot is the bot that automatically assigns incoming PRs to reviewers.
+If you are added, you will be randomly selected to review
+PRs. If you find you are assigned a PR that you don't feel comfortable
+reviewing, you can also leave a comment like r? @so-and-so
to assign
+to someone else — if you don't know who to request, just write r? @nikomatsakis for reassignment
and @nikomatsakis will pick someone
+for you.
Getting on the reviewer rotation is much appreciated as it lowers the +review burden for all of us! However, if you don't have time to give +people timely feedback on their PRs, it may be better that you don't +get on the list.
+Full team membership is typically extended once someone made many +contributions to the Rust compiler over time, ideally (but not +necessarily) to multiple areas. Sometimes this might be implementing a +new feature, but it is also important — perhaps more important! — to +have time and willingness to help out with general upkeep such as +bugfixes, tracking regressions, and other less glamorous work.
+ +Redirecting to... tests/compiletest.html.
+ + diff --git a/const-eval.html b/const-eval.html new file mode 100644 index 000000000..5bf3cb0b9 --- /dev/null +++ b/const-eval.html @@ -0,0 +1,253 @@ + + + + + +Constant evaluation is the process of computing values at compile time. For a +specific item (constant/static/array length) this happens after the MIR for the +item is borrow-checked and optimized. In many cases trying to const evaluate an +item will trigger the computation of its MIR for the first time.
+Prominent examples are:
+static
Additionally constant evaluation can be used to reduce the workload or binary +size at runtime by precomputing complex operations at compiletime and only +storing the result.
+All uses of constant evaluation can either be categorized as "influencing the type system" +(array lengths, enum variant discriminants, const generic parameters), or as solely being +done to precompute expressions to be used at runtime.
+Constant evaluation can be done by calling the const_eval_*
functions of TyCtxt
.
+They're the wrappers of the const_eval
query.
const_eval_global_id_for_typeck
evaluates a constant to a valtree,
+so the result value can be further inspected by the compiler.const_eval_global_id
evaluate a constant to an "opaque blob" containing its final value;
+this is only useful for codegen backends and the CTFE evaluator engine itself.eval_static_initializer
specifically computes the initial values of a static.
+Statics are special; all other functions do not represent statics correctly
+and have thus assertions preventing their use on statics.The const_eval_*
functions use a ParamEnv
of environment
+in which the constant is evaluated (e.g. the function within which the constant is used)
+and a GlobalId
. The GlobalId
is made up of an Instance
referring to a constant
+or static or of an Instance
of a function and an index into the function's Promoted
table.
Constant evaluation returns an EvalToValTreeResult
for type system constants
+or EvalToConstValueResult
with either the error, or a representation of the
+evaluated constant: a valtree or a MIR constant
+value, respectively.
The interpreter is a virtual machine for executing MIR without compiling to
+machine code. It is usually invoked via tcx.const_eval_*
functions. The
+interpreter is shared between the compiler (for compile-time function
+evaluation, CTFE) and the tool Miri, which
+uses the same virtual machine to detect Undefined Behavior in (unsafe) Rust
+code.
If you start out with a constant:
++#![allow(unused)] +fn main() { +const FOO: usize = 1 << 12; +} +
rustc doesn't actually invoke anything until the constant is either used or +placed into metadata.
+Once you have a use-site like:
+type Foo = [u8; FOO - 42];
+
+The compiler needs to figure out the length of the array before being able to +create items that use the type (locals, constants, function arguments, ...).
+To obtain the (in this case empty) parameter environment, one can call
+let param_env = tcx.param_env(length_def_id);
. The GlobalId
needed is
let gid = GlobalId {
+ promoted: None,
+ instance: Instance::mono(length_def_id),
+};
+
+Invoking tcx.const_eval(param_env.and(gid))
will now trigger the creation of
+the MIR of the array length expression. The MIR will look something like this:
Foo::{{constant}}#0: usize = {
+ let mut _0: usize;
+ let mut _1: (usize, bool);
+
+ bb0: {
+ _1 = CheckedSub(const FOO, const 42usize);
+ assert(!move (_1.1: bool), "attempt to subtract with overflow") -> bb1;
+ }
+
+ bb1: {
+ _0 = move (_1.0: usize);
+ return;
+ }
+}
+
+Before the evaluation, a virtual memory location (in this case essentially a
+vec![u8; 4]
or vec![u8; 8]
) is created for storing the evaluation result.
At the start of the evaluation, _0
and _1
are
+Operand::Immediate(Immediate::Scalar(ScalarMaybeUndef::Undef))
. This is quite
+a mouthful: Operand
can represent either data stored somewhere in the
+interpreter memory (Operand::Indirect
), or (as an optimization)
+immediate data stored in-line. And Immediate
can either be a single
+(potentially uninitialized) scalar value (integer or thin pointer),
+or a pair of two of them. In our case, the single scalar value is not (yet)
+initialized.
When the initialization of _1
is invoked, the value of the FOO
constant is
+required, and triggers another call to tcx.const_eval_*
, which will not be shown
+here. If the evaluation of FOO is successful, 42
will be subtracted from its
+value 4096
and the result stored in _1
as
+Operand::Immediate(Immediate::ScalarPair(Scalar::Raw { data: 4054, .. }, Scalar::Raw { data: 0, .. })
. The first part of the pair is the computed value,
+the second part is a bool that's true if an overflow happened. A Scalar::Raw
+also stores the size (in bytes) of this scalar value; we are eliding that here.
The next statement asserts that said boolean is 0
. In case the assertion
+fails, its error message is used for reporting a compile-time error.
Since it does not fail, Operand::Immediate(Immediate::Scalar(Scalar::Raw { data: 4054, .. }))
is stored in the virtual memory it was allocated before the
+evaluation. _0
always refers to that location directly.
After the evaluation is done, the return value is converted from Operand
to
+ConstValue
by op_to_const
: the former representation is geared towards
+what is needed during const evaluation, while ConstValue
is shaped by the
+needs of the remaining parts of the compiler that consume the results of const
+evaluation. As part of this conversion, for types with scalar values, even if
+the resulting Operand
is Indirect
, it will return an immediate
+ConstValue::Scalar(computed_value)
(instead of the usual ConstValue::ByRef
).
+This makes using the result much more efficient and also more convenient, as no
+further queries need to be executed in order to get at something as simple as a
+usize
.
Future evaluations of the same constants will not actually invoke +the interpreter, but just use the cached result.
+The interpreter's outside-facing datastructures can be found in
+rustc_middle/src/mir/interpret.
+This is mainly the error enum and the ConstValue
and Scalar
types. A
+ConstValue
can be either Scalar
(a single Scalar
, i.e., integer or thin
+pointer), Slice
(to represent byte slices and strings, as needed for pattern
+matching) or ByRef
, which is used for anything else and refers to a virtual
+allocation. These allocations can be accessed via the methods on
+tcx.interpret_interner
. A Scalar
is either some Raw
integer or a pointer;
+see the next section for more on that.
If you are expecting a numeric result, you can use eval_usize
(panics on
+anything that can't be represented as a u64
) or try_eval_usize
which results
+in an Option<u64>
yielding the Scalar
if possible.
To support any kind of pointers, the interpreter needs to have a "virtual memory" that the
+pointers can point to. This is implemented in the Memory
type. In the
+simplest model, every global variable, stack variable and every dynamic
+allocation corresponds to an Allocation
in that memory. (Actually using an
+allocation for every MIR stack variable would be very inefficient; that's why we
+have Operand::Immediate
for stack variables that are both small and never have
+their address taken. But that is purely an optimization.)
Such an Allocation
is basically just a sequence of u8
storing the value of
+each byte in this allocation. (Plus some extra data, see below.) Every
+Allocation
has a globally unique AllocId
assigned in Memory
. With that, a
+Pointer
consists of a pair of an AllocId
(indicating the allocation) and
+an offset into the allocation (indicating which byte of the allocation the
+pointer points to). It may seem odd that a Pointer
is not just an integer
+address, but remember that during const evaluation, we cannot know at which
+actual integer address the allocation will end up -- so we use AllocId
as
+symbolic base addresses, which means we need a separate offset. (As an aside,
+it turns out that pointers at run-time are
+more than just integers, too.)
These allocations exist so that references and raw pointers have something to
+point to. There is no global linear heap in which things are allocated, but each
+allocation (be it for a local variable, a static or a (future) heap allocation)
+gets its own little memory with exactly the required size. So if you have a
+pointer to an allocation for a local variable a
, there is no possible (no
+matter how unsafe) operation that you can do that would ever change said pointer
+to a pointer to a different local variable b
.
+Pointer arithmetic on a
will only ever change its offset; the AllocId
stays the same.
This, however, causes a problem when we want to store a Pointer
into an
+Allocation
: we cannot turn it into a sequence of u8
of the right length!
+AllocId
and offset together are twice as big as a pointer "seems" to be. This
+is what the relocation
field of Allocation
is for: the byte offset of the
+Pointer
gets stored as a bunch of u8
, while its AllocId
gets stored
+out-of-band. The two are reassembled when the Pointer
is read from memory.
+The other bit of extra data an Allocation
needs is undef_mask
for keeping
+track of which of its bytes are initialized.
Memory
exists only during evaluation; it gets destroyed when the
+final value of the constant is computed. In case that constant contains any
+pointers, those get "interned" and moved to a global "const eval memory" that is
+part of TyCtxt
. These allocations stay around for the remaining computation
+and get serialized into the final output (so that dependent crates can use
+them).
Moreover, to also support function pointers, the global memory in TyCtxt
can
+also contain "virtual allocations": instead of an Allocation
, these contain an
+Instance
. That allows a Pointer
to point to either normal data or a
+function, which is needed to be able to evaluate casts from function pointers to
+raw pointers.
Finally, the GlobalAlloc
type used in the global memory also contains a
+variant Static
that points to a particular const
or static
item. This is
+needed to support circular statics, where we need to have a Pointer
to a
+static
for which we cannot yet have an Allocation
as we do not know the
+bytes of its value.
One common cause of confusion in the interpreter is that being a pointer value and having
+a pointer type are entirely independent properties. By "pointer value", we
+refer to a Scalar::Ptr
containing a Pointer
and thus pointing somewhere into
+the interpreter's virtual memory. This is in contrast to Scalar::Raw
, which is just some
+concrete integer.
However, a variable of pointer or reference type, such as *const T
or &T
,
+does not have to have a pointer value: it could be obtained by casting or
+transmuting an integer to a pointer.
+And similarly, when casting or transmuting a reference to some
+actual allocation to an integer, we end up with a pointer value
+(Scalar::Ptr
) at integer type (usize
). This is a problem because we
+cannot meaningfully perform integer operations such as division on pointer
+values.
Although the main entry point to constant evaluation is the tcx.const_eval_*
+functions, there are additional functions in
+rustc_const_eval/src/const_eval
+that allow accessing the fields of a ConstValue
(ByRef
or otherwise). You should
+never have to access an Allocation
directly except for translating it to the
+compilation target (at the moment just LLVM).
The interpreter starts by creating a virtual stack frame for the current constant that is +being evaluated. There's essentially no difference between a constant and a +function with no arguments, except that constants do not allow local (named) +variables at the time of writing this guide.
+A stack frame is defined by the Frame
type in
+rustc_const_eval/src/interpret/eval_context.rs
+and contains all the local
+variables memory (None
at the start of evaluation). Each frame refers to the
+evaluation of either the root constant or subsequent calls to const fn
. The
+evaluation of another constant simply calls tcx.const_eval_*
, which produce an
+entirely new and independent stack frame.
The frames are just a Vec<Frame>
, there's no way to actually refer to a
+Frame
's memory even if horrible shenanigans are done via unsafe code. The only
+memory that can be referred to are Allocation
s.
The interpreter now calls the step
method (in
+rustc_const_eval/src/interpret/step.rs
+) until it either returns an error or has no further statements to execute. Each
+statement will now initialize or modify the locals or the virtual memory
+referred to by a local. This might require evaluating other constants or
+statics, which just recursively invokes tcx.const_eval_*
.
Constants used in the type system are represented as ty::Const
.
+The variants of their const_kind::ConstKind
mostly mirror the variants of ty_kind::TyKind
+with the two additional variants being ConstKind::Value
and ConstKind::Unevaluated
.
WithOptConstParam
and dealing with the query systemTo typecheck constants used in the type system, we have to know their expected type.
+For const arguments in type dependent paths, e.g. x.foo::<{ 3 + 4 }>()
, we don't know
+the expected type for { 3 + 4 }
until we are typechecking the containing function.
As we may however have to evaluate that constant during this typecheck, we would get a cycle error. +For more details, you can look at this document.
+This section talks about what's happening with feature(generic_const_exprs)
enabled.
+On stable we do not yet supply any generic parameters to anonymous constants,
+avoiding most of the issues mentioned here.
Unless a constant is either a simple literal, e.g. [u8; 3]
or foo::<{ 'c' }>()
,
+or a generic parameter, e.g. [u8; N]
, converting a constant to its ty::Const
representation
+returns an unevaluated constant. Even fully concrete constants which do not depend on
+generic parameters are not evaluated right away.
Anonymous constants are typechecked separately from their containing item, e.g.
++#![allow(unused)] +fn main() { +fn foo<const N: usize>() -> [u8; N + 1] { + [0; N + 1] +} +} +
is treated as
++#![allow(unused)] +fn main() { +const ANON_CONST_1<const N: usize> = N + 1; +const ANON_CONST_2<const N: usize> = N + 1; +fn foo<const N: usize>() -> [u8; ANON_CONST_1::<N>] { + [0; ANON_CONST_2::<N>] +} +} +
For the compiler, ANON_CONST_1
and ANON_CONST_2
are completely different, so
+we have to somehow look into unevaluated constants to check whether they should
+unify.
For this we use InferCtxt::try_unify_abstract_consts. +This builds a custom AST for the two inputs from their THIR. This is then used for +the actual comparison.
+We do not eagerly evaluate constant as they can be used in the where
-clauses of their
+parent item, for example:
+#![allow(unused)] +fn main() { +#[feature(generic_const_exprs)] +fn foo<T: Trait>() +where + [u8; <T as Trait>::ASSOC + 1]: SomeOtherTrait, +{} +} +
The constant <T as Trait>::ASSOC + 1
depends on the T: Trait
bound of
+its parents caller bounds, but is also part of another bound itself.
+If we were to eagerly evaluate this constant while computing its parents bounds
+this would cause a query cycle.
Anonymous constants inherit the generic parameters of their parent, which is
+why the array length in foo<const N: usize>() -> [u8; N + 1]
can use N
.
Without any manual adjustments, this causes us to include parameters even if +the constant doesn't use them in any way. This can cause +some interesting errors and breaks some already stable code.
+ +While bugs are unfortunate, they're a reality in software. We can't fix what we +don't know about, so please report liberally. If you're not sure if something +is a bug or not, feel free to file a bug anyway.
+If you believe reporting your bug publicly represents a security risk to Rust users, +please follow our instructions for reporting security vulnerabilities.
+If you're using the nightly channel, please check if the bug exists in the +latest toolchain before filing your bug. It might be fixed already.
+If you have the chance, before reporting a bug, please search existing issues, +as it's possible that someone else has already reported your error. This doesn't +always work, and sometimes it's hard to know what to search for, so consider this +extra credit. We won't mind if you accidentally file a duplicate report.
+Similarly, to help others who encountered the bug find your issue, consider +filing an issue with a descriptive title, which contains information that might +be unique to it. This can be the language or compiler feature used, the +conditions that trigger the bug, or part of the error message if there is any. +An example could be: "impossible case reached" on lifetime inference for impl +Trait in return position.
+Opening an issue is as easy as following this +link and filling out the fields +in the appropriate provided template.
+For most PRs, no special procedures are needed. You can just open a PR, and it +will be reviewed, approved, and merged. This includes most bug fixes, +refactorings, and other user-invisible changes. The next few sections talk +about exceptions to this rule.
+Also, note that it is perfectly acceptable to open WIP PRs or GitHub Draft PRs. +Some people prefer to do this so they can get feedback along the +way or share their code with a collaborator. Others do this so they can utilize +the CI to build and test their PR (e.g. when developing on a slow machine).
+Rust has strong backwards-compatibility guarantees. Thus, new features can't +just be implemented directly in stable Rust. Instead, we have 3 release +channels: stable, beta, and nightly.
+master
branch of the repo. This is the only
+channel where unstable, incomplete, or experimental features are usable with
+feature gates.See this chapter on implementing new features for more +information.
+Breaking changes have a dedicated section in the dev-guide.
+The compiler team has a special process for large changes, whether or not they +cause breakage. This process is called a Major Change Proposal (MCP). MCP is a +relatively lightweight mechanism for getting feedback on large changes to the +compiler (as opposed to a full RFC or a design meeting with the team).
+Example of things that might require MCPs include major refactorings, changes +to important types, or important changes to how the compiler does something, or +smaller user-facing changes.
+When in doubt, ask on zulip. It would be a shame to put a lot of work +into a PR that ends up not getting merged! See this document for +more info on MCPs.
+Compiler performance is important. We have put a lot of effort over the last +few years into gradually improving it.
+If you suspect that your change may cause a performance regression (or +improvement), you can request a "perf run" (and your reviewer may also request one +before approving). This is yet another bot that will compile a collection of +benchmarks on a compiler with your changes. The numbers are reported +here, and you can see a comparison of your changes against the latest +master.
+++For an introduction to the performance of Rust code in general +which would also be useful in rustc development, see The Rust Performance Book.
+
Pull requests (or PRs for short) are the primary mechanism we use to change Rust. +GitHub itself has some great documentation on using the +Pull Request feature. We use the "fork and pull" model described here, +where contributors push changes to their personal fork and create pull requests to +bring those changes into the source repository. We have more info about how to use git +when contributing to Rust under the git section.
+++Advice for potentially large, complex, cross-cutting and/or very domain-specific changes
+The compiler reviewers on rotation usually each have areas of the compiler that they know well, +but also have areas that they are not very familiar with. If your PR contains changes that are +large, complex, cross-cutting and/or highly domain-specific, it becomes very difficult to find a +suitable reviewer who is comfortable in reviewing all of the changes in such a PR. This is also +true if the changes are not only compiler-specific but also contains changes which fall under the +purview of reviewers from other teams, like the standard library team. There's a bot +which notifies the relevant teams and pings people who have setup specific alerts based on the +files modified.
+Before making such changes, you are strongly encouraged to discuss your proposed changes with +the compiler team beforehand (and with other teams that the changes would require approval +from), and work with the compiler team to see if we can help you break down a large potentially +unreviewable PR into a series of smaller more individually reviewable PRs.
+You can communicate with the compiler team by creating a #t-compiler thread on zulip +to discuss your proposed changes.
+Communicating with the compiler team beforehand helps in several ways:
++
+- It increases the likelihood of your PRs being reviewed in a timely manner. +
++
+- We can help you identify suitable reviewers before you open actual PRs, or help find +advisors and liaisons to help you navigate the change procedures, or help with running +try-jobs, perf runs and crater runs as suitable.
+- It helps the compiler team track your changes.
+- The compiler team can perform vibe checks on your changes early and often, to see if the +direction of the changes align with what the compiler team prefers to see.
+- Helps to avoid situations where you may have invested significant time and effort into large +changes that the compiler team might not be willing to accept, or finding out very late that the +changes are in a direction that the compiler team disagrees with.
+
All pull requests are reviewed by another person. We have a bot, +@rustbot, that will automatically assign a random person +to review your request based on which files you changed.
+If you want to request that a specific person reviews your pull request, you
+can add an r?
to the pull request description or in a comment. For example,
+if you want to ask a review to @awesome-reviewer, add
r? @awesome-reviewer
+
+to the end of the pull request description, and @rustbot will assign +them instead of a random person. This is entirely optional.
+You can also assign a random reviewer from a specific team by writing r? rust-lang/groupname
.
+As an example,
+if you were making a diagnostics change,
+then you could get a reviewer from the diagnostics team by adding:
r? rust-lang/diagnostics
+
+For a full list of possible groupname
s,
+check the adhoc_groups
section at the triagebot.toml config file,
+or the list of teams in the rust-lang teams database.
++NOTE
+Pull request reviewers are often working at capacity, +and many of them are contributing on a volunteer basis. +In order to minimize review delays, +pull request authors and assigned reviewers should ensure that the review label +(
+S-waiting-on-review
andS-waiting-on-author
) stays updated, +invoking these commands when appropriate:+
+- +
++
@rustbot author
: +the review is finished, +and PR author should check the comments and take action accordingly.- +
++
@rustbot review
: +the author is ready for a review, +and this PR will be queued again in the reviewer's queue.
Please note that the reviewers are humans, who for the most part work on rustc
+in their free time. This means that they can take some time to respond and review
+your PR. It also means that reviewers can miss some PRs that are assigned to them.
To try to move PRs forward, the Triage WG regularly goes through all PRs that +are waiting for review and haven't been discussed for at least 2 weeks. If you +don't get a review within 2 weeks, feel free to ask the Triage WG on +Zulip (#t-release/triage). They have knowledge of when to ping, who might be +on vacation, etc.
+The reviewer may request some changes using the GitHub code review interface. +They may also request special procedures for some PRs. +See Crater and Breaking Changes chapters for some examples of such procedures.
+In addition to being reviewed by a human, pull requests are automatically tested, +thanks to continuous integration (CI). Basically, every time you open and update +a pull request, CI builds the compiler and tests it against the +compiler test suite, and also performs other tests such as checking that +your pull request is in compliance with Rust's style guidelines.
+Running continuous integration tests allows PR authors to catch mistakes early +without going through a first review cycle, and also helps reviewers stay aware +of the status of a particular pull request.
+Rust has plenty of CI capacity, and you should never have to worry about wasting
+computational resources each time you push a change. It is also perfectly fine
+(and even encouraged!) to use the CI to test your changes if it can help your
+productivity. In particular, we don't recommend running the full ./x test
suite locally,
+since it takes a very long time to execute.
After someone has reviewed your pull request, they will leave an annotation
+on the pull request with an r+
. It will look something like this:
@bors r+
+
+This tells @bors, our lovable integration bot, that your pull request has
+been approved. The PR then enters the merge queue, where @bors
+will run all the tests on every platform we support. If it all works out,
+@bors will merge your code into master
and close the pull request.
Depending on the scale of the change, you may see a slightly different form of r+
:
@bors r+ rollup
+
+The additional rollup
tells @bors that this change should always be "rolled up".
+Changes that are rolled up are tested and merged alongside other PRs, to
+speed the process up. Typically only small changes that are expected not to conflict
+with one another are marked as "always roll up".
Be patient; this can take a while and the queue can sometimes be long. PRs are never merged by hand.
+You are now ready to file a pull request? Great! Here are a few points you +should be aware of.
+All pull requests should be filed against the master
branch,
+unless you know for sure that you should target a different branch.
Make sure your pull request is in compliance with Rust's style guidelines by running
+$ ./x test tidy --bless
+
+We recommend to make this check before every pull request (and every new commit +in a pull request); you can add git hooks +before every push to make sure you never forget to make this check. +The CI will also run tidy and will fail if tidy fails.
+Rust follows a no merge-commit policy, meaning, when you encounter merge
+conflicts you are expected to always rebase instead of merging. E.g. always use
+rebase when bringing the latest changes from the master branch to your feature
+branch. If your PR contains merge commits, it will get marked as has-merge-commits
.
+Once you have removed the merge commits, e.g., through an interactive rebase, you
+should remove the label again:
@rustbot label -has-merge-commits
+
+See this chapter for more details.
+If you encounter merge conflicts or when a reviewer asks you to perform some
+changes, your PR will get marked as S-waiting-on-author
. When you resolve
+them, you should use @rustbot
to mark it as S-waiting-on-review
:
@rustbot ready
+
+GitHub allows closing issues using keywords. This feature +should be used to keep the issue tracker tidy. However, it is generally preferred +to put the "closes #123" text in the PR description rather than the issue commit; +particularly during rebasing, citing the issue number in the commit can "spam" +the issue in question.
+However, if your PR fixes a stable-to-beta or stable-to-stable regression and has
+been accepted for a beta and/or stable backport (i.e., it is marked beta-accepted
+and/or stable-accepted
), please do not use any such keywords since we don't
+want the corresponding issue to get auto-closed once the fix lands on master.
+Please update the PR description while still mentioning the issue somewhere.
+For example, you could write Fixes (after beta backport) #NNN.
.
As for further actions, please keep a sharp look-out for a PR whose title begins with
+[beta]
or [stable]
and which backports the PR in question. When that one gets
+merged, the relevant issue can be closed. The closing comment should mention all
+PRs that were involved. If you don't have the permissions to close the issue, please
+leave a comment on the original PR asking the reviewer to close it for you.
When a PR leads to miscompile, significant performance regressions, or other critical issues, we may +want to revert that PR with a regression test case. You can also check out the revert policy on +Forge docs (which is mainly targeted for reviewers, but contains useful info for PR authors too).
+If the PR contains huge changes, it can be challenging to revert, making it harder to review +incremental fixes in subsequent updates. Or if certain code in that PR is heavily depended upon by +subsequent PRs, reverting it can become difficult.
+In such cases, we can identify the problematic code and disable it for some input, as shown in #128271.
+For MIR optimizations, we can also use the -Zunsound-mir-opt
option to gate the mir-opt, as shown
+in #132356.
This section has moved to "Using External Repositories".
+Documentation improvements are very welcome. The source of doc.rust-lang.org
+is located in src/doc
in the tree, and standard API documentation is generated
+from the source code itself (e.g. library/std/src/lib.rs
). Documentation pull requests
+function in the same way as other pull requests.
To find documentation-related issues, sort by the A-docs label.
+You can find documentation style guidelines in RFC 1574.
+To build the standard library documentation, use x doc --stage 0 library --open
.
+To build the documentation for a book (e.g. the unstable book), use x doc src/doc/unstable-book.
+Results should appear in build/host/doc
, as well as automatically open in your default browser.
+See Building Documentation for more
+information.
You can also use rustdoc
directly to check small fixes. For example,
+rustdoc src/doc/reference.md
will render reference to doc/reference.html
.
+The CSS might be messed up, but you can verify that the HTML is right.
Contributions to the rustc-dev-guide are always welcome, and can be made directly at +the rust-lang/rustc-dev-guide repo. +The issue tracker in that repo is also a great way to find things that need doing. +There are issues for beginners and advanced compiler devs alike!
+Just a few things to keep in mind:
+Please try to avoid overly long lines and use semantic line breaks (where you break the line after each sentence). +There is no strict limit on line lengths; let the sentence or part of the sentence flow to its proper end on the same line.
+When contributing text to the guide, please contextualize the information with some time period +and/or a reason so that the reader knows how much to trust or mistrust the information. +Aim to provide a reasonable amount of context, possibly including but not limited to:
+A reason for why the data may be out of date other than "change", +as change is a constant across the project.
+The date the comment was added, e.g. instead of writing "Currently, ..." +or "As of now, ...", +consider adding the date, in one of the following formats:
+There is a CI action (in ~/.github/workflows/date-check.yml
)
+that generates a monthly showing those that are over 6 months old
+(example).
For the action to pick the date, +add a special annotation before specifying the date:
+<!-- date-check --> Sep 2024
+
+Example:
+As of <!-- date-check --> Sep 2024, the foo did the bar.
+
+For cases where the date should not be part of the visible rendered output, +use the following instead:
+<!-- date-check: Sep 2024 -->
+
+A link to a relevant WG, tracking issue, rustc
rustdoc page, or similar, that may provide
+further explanation for the change process or a way to verify that the information is not
+outdated.
If a text grows rather long (more than a few page scrolls) or complicated (more than four
+subsections),
+it might benefit from having a Table of Contents at the beginning,
+which you can auto-generate by including the <!-- toc -->
marker at the top.
Sometimes, an issue will stay open, even though the bug has been fixed. +And sometimes, the original bug may go stale because something has changed in the meantime.
+It can be helpful to go through older bug reports and make sure that they are still valid. +Load up an older issue, double check that it's still true, +and leave a comment letting us know if it is or is not. +The least recently updated sort is good for finding issues like this.
+Thanks to @rustbot
, anyone can help triage issues by adding
+appropriate labels to issues that haven't been triaged yet:
Labels | Color | Description |
---|---|---|
A- | Yellow | The area of the project an issue relates to. |
B- | Magenta | Issues which are blockers. |
beta- | Dark Blue | Tracks changes which need to be backported to beta |
C- | Light Purple | The category of an issue. |
D- | Mossy Green | Issues for diagnostics. |
E- | Green | The experience level necessary to fix an issue. |
F- | Peach | Issues for nightly features. |
I- | Red | The importance of the issue. |
I-*-nominated | Red | The issue has been nominated for discussion at the next meeting of the corresponding team. |
I-prioritize | Red | The issue has been nominated for prioritization by the team tagged with a T-prefixed label. |
L- | Teal | The relevant lint. |
metabug | Purple | Bugs that collect other bugs. |
O- | Purple Grey | The operating system or platform that the issue is specific to. |
P- | Orange | The issue priority. These labels can be assigned by anyone that understand the issue and is able to prioritize it, and remove the I-prioritize label. |
regression- | Pink | Tracks regressions from a stable release. |
relnotes | Light Orange | Changes that should be documented in the release notes of the next release. |
S- | Gray | Tracks the status of pull requests. |
S-tracking- | Steel Blue | Tracks the status of tracking issues. |
stable- | Dark Blue | Tracks changes which need to be backported to stable in anticipation of a point release. |
T- | Blue | Denotes which team the issue belongs to. |
WG- | Green | Denotes which working group the issue belongs to. |
rfcbot uses its own labels for tracking the process of coordinating +asynchronous decisions, such as approving or rejecting a change. +This is used for RFCs, issues, and pull requests.
+Labels | Color | Description |
---|---|---|
proposed-final-comment-period | Gray | Currently awaiting signoff of all team members in order to enter the final comment period. |
disposition-merge | Green | Indicates the intent is to merge the change. |
disposition-close | Red | Indicates the intent is to not accept the change and close it. |
disposition-postpone | Gray | Indicates the intent is to not accept the change at this time and postpone it to a later date. |
final-comment-period | Blue | Currently soliciting final comments before merging or closing. |
finished-final-comment-period | Light Yellow | The final comment period has concluded, and the issue will be merged or closed. |
postponed | Yellow | The issue has been postponed. |
closed | Red | The issue has been rejected. |
to-announce | Gray | Issues that have finished their final-comment-period and should be publicly announced. Note: the rust-lang/rust repository uses this label differently, to announce issues at the triage meetings. |
This section has moved to the "About this guide" chapter.
+ +This file offers some tips on the coding conventions for rustc. This +chapter covers formatting, coding for correctness, +using crates from crates.io, and some tips on +structuring your PR for easy review.
+ +rustc is moving towards the Rust standard coding style.
+However, for now we don't use stable rustfmt
; we use a pinned version with a
+special config, so this may result in different style from normal rustfmt
.
+Therefore, formatting this repository using cargo fmt
is not recommended.
Instead, formatting should be done using ./x fmt
. It's a good habit to run
+./x fmt
before every commit, as this reduces conflicts later.
Formatting is checked by the tidy
script. It runs automatically when you do
+./x test
and can be run in isolation with ./x fmt --check
.
If you want to use format-on-save in your editor, the pinned version of
+rustfmt
is built under build/<target>/stage0/bin/rustfmt
. You'll have to
+pass the --edition=2021
argument yourself when calling
+rustfmt
directly.
The compiler contains some C++ code for interfacing with parts of LLVM that +don't have a stable C API. +When modifying that code, use this command to format it:
+./x test tidy --extra-checks=cpp:fmt --bless
+
+This uses a pinned version of clang-format
, to avoid relying on the local
+environment.
In the past, files began with a copyright and license notice. Please omit +this notice for new files licensed under the standard terms (dual +MIT/Apache-2.0).
+All of the copyright notices should be gone by now, but if you come across one +in the rust-lang/rust repo, feel free to open a PR to remove it.
+Lines should be at most 100 characters. It's even better if you can +keep things to 80.
+Ignoring the line length limit. Sometimes – in particular for +tests – it can be necessary to exempt yourself from this limit. In +that case, you can add a comment towards the top of the file like so:
++#![allow(unused)] +fn main() { +// ignore-tidy-linelength +} +
Prefer 4-space indent.
+ +Beyond formatting, there are a few other tips that are worth +following.
+Using _
in a match is convenient, but it means that when new
+variants are added to the enum, they may not get handled correctly.
+Ask yourself: if a new variant were added to this enum, what's the
+chance that it would want to use the _
code, versus having some
+other treatment? Unless the answer is "low", then prefer an
+exhaustive match. (The same advice applies to if let
and while let
, which are effectively tests for a single variant.)
As a useful tool to yourself, you can insert a // TODO
comment
+for something that you want to get back to before you land your PR:
fn do_something() {
+ if something_else {
+ unimplemented!(); // TODO write this
+ }
+}
+
+The tidy script will report an error for a // TODO
comment, so this
+code would not be able to land until the TODO is fixed (or removed).
This can also be useful in a PR as a way to signal from one commit that you are +leaving a bug that a later commit will fix:
+if foo {
+ return true; // TODO wrong, but will be fixed in a later commit
+}
+
+
+See the crates.io dependencies section.
+ +How you prepare the commits in your PR can make a big difference for the +reviewer. Here are some tips.
+Isolate "pure refactorings" into their own commit. For example, if +you rename a method, then put that rename into its own commit, along +with the renames of all the uses.
+More commits is usually better. If you are doing a large change, +it's almost always better to break it up into smaller steps that can +be independently understood. The one thing to be aware of is that if +you introduce some code following one strategy, then change it +dramatically (versus adding to it) in a later commit, that +'back-and-forth' can be confusing.
+Format liberally. While only the final commit of a PR must be correctly
+formatted, it is both easier to review and less noisy to format each commit
+individually using ./x fmt
.
No merges. We do not allow merge commits into our history, other
+than those by bors. If you get a merge conflict, rebase instead via a
+command like git rebase -i rust-lang/master
(presuming you use the
+name rust-lang
for your remote).
Individual commits do not have to build (but it's nice). We do not +require that every intermediate commit successfully builds – we only +expect to be able to bisect at a PR level. However, if you can make +individual commits build, that is always helpful.
+Apart from normal Rust style/naming conventions, there are also some specific +to the compiler.
+cx
tends to be short for "context" and is often used as a suffix. For
+example, tcx
is a common name for the Typing Context.
'tcx
is used as the lifetime name for the Typing Context.
Because crate
is a keyword, if you need a variable to represent something
+crate-related, often the spelling is changed to krate
.
Please read RFC 3668 to understand the general motivation of the feature. This is a very technical and somewhat "vertical" chapter; ideally we'd split this and sprinkle it across all the relevant chapters, but for the purposes of understanding async closures holistically, I've put this together all here in one chapter.
+Coroutine-closures are a generalization of async closures, being special syntax for closure expressions which return a coroutine, notably one that is allowed to capture from the closure's upvars.
+For now, the only usable kind of coroutine-closure is the async closure, and supporting async closures is the extent of this PR. We may eventually support gen || {}
, etc., and most of the problems and curiosities described in this document apply to all coroutine-closures in general.
As a consequence of the code being somewhat general, this document may flip between calling them "async closures" and "coroutine-closures". The future that is returned by the async closure will generally be called the "coroutine" or the "child coroutine".
+Async closures (and in the future, other coroutine flavors such as gen
) are represented in HIR as a hir::Closure
whose closure-kind is ClosureKind::CoroutineClosure(_)
1, which wraps an async block, which is also represented in HIR as a hir::Closure
) and whose closure-kind is ClosureKind::Closure(CoroutineKind::Desugared(_, CoroutineSource::Closure))
2.
Like async fn
, when lowering an async closure's body, we need to unconditionally move all of the closures arguments into the body so they are captured. This is handled by lower_coroutine_body_with_moved_arguments
3. The only notable quirk with this function is that the async block we end up generating as a capture kind of CaptureBy::ByRef
4. We later force all of the closure args to be captured by-value5, but we don't want the whole async block to act as if it were an async move
, since that would defeat the purpose of the self-borrowing of an async closure.
rustc_middle::ty
RepresentationFor the purposes of keeping the implementation mostly future-compatible (i.e. with gen || {}
and async gen || {}
), most of this section calls async closures "coroutine-closures".
The main thing that this PR introduces is a new TyKind
called CoroutineClosure
6 and corresponding variants on other relevant enums in typeck and borrowck (UpvarArgs
, DefiningTy
, AggregateKind
).
We introduce a new TyKind
instead of generalizing the existing TyKind::Closure
due to major representational differences in the type. The major differences between CoroutineClosure
s can be explored by first inspecting the CoroutineClosureArgsParts
, which is the "unpacked" representation of the coroutine-closure's generics.
Like a closure, we have parent_args
, a closure_kind_ty
, and a tupled_upvars_ty
. These represent the same thing as their closure counterparts; namely: the generics inherited from the body that the closure is defined in, the maximum "calling capability" of the closure (i.e. must it be consumed to be called, like FnOnce
, or can it be called by-ref), and the captured upvars of the closure itself.
A traditional closure has a fn_sig_as_fn_ptr_ty
which it uses to represent the signature of the closure. In contrast, we store the signature of a coroutine closure in a somewhat "exploded" way, since coroutine-closures have two signatures depending on what AsyncFn*
trait you call it with (see below sections).
Conceptually, the coroutine-closure may be thought as containing several different signature types depending on whether it is being called by-ref or by-move.
+To conveniently recreate both of these signatures, the signature_parts_ty
stores all of the relevant parts of the coroutine returned by this coroutine-closure. This signature parts type will have the general shape of fn(tupled_inputs, resume_ty) -> (return_ty, yield_ty)
, where resume_ty
, return_ty
, and yield_ty
are the respective types for the coroutine returned by the coroutine-closure7.
The compiler mainly deals with the CoroutineClosureSignature
type8, which is created by extracting the relevant types out of the fn()
ptr type described above, and which exposes methods that can be used to construct the coroutine that the coroutine-closure ultimately returns.
Coroutine
return typeAlong with the data stored in the signature, to construct a TyKind::Coroutine
to return, we also need to store the "witness" of the coroutine.
So what about the upvars of the Coroutine
that is returned? Well, for AsyncFnOnce
(i.e. call-by-move), this is simply the same upvars that the coroutine returns. But for AsyncFnMut
/AsyncFn
, the coroutine that is returned from the coroutine-closure borrows data from the coroutine-closure with a given "environment" lifetime9. This corresponds to the &self
lifetime10 on the AsyncFnMut
/AsyncFn
call signature, and the GAT lifetime of the ByRef
11.
To most easily construct the Coroutine
that a coroutine-closure returns, you can use the to_coroutine_given_kind_and_upvars
12 helper on CoroutineClosureSignature
, which can be acquired from the CoroutineClosureArgs
.
Most of the args to that function will be components that you can get out of the CoroutineArgs
, except for the goal_kind: ClosureKind
which controls which flavor of coroutine to return based off of the ClosureKind
passed in -- i.e. it will prepare the by-ref coroutine if ClosureKind::Fn | ClosureKind::FnMut
, and the by-move coroutine if ClosureKind::FnOnce
.
We introduce a parallel hierarchy of Fn*
traits that are implemented for . The motivation for the introduction was covered in a blog post: Async Closures.
All currently-stable callable types (i.e., closures, function items, function pointers, and dyn Fn*
trait objects) automatically implement AsyncFn*() -> T
if they implement Fn*() -> Fut
for some output type Fut
, and Fut
implements Future<Output = T>
13.
Async closures implement AsyncFn*
as their bodies permit; i.e. if they end up using upvars in a way that is compatible (i.e. if they consume or mutate their upvars, it may affect whether they implement AsyncFn
and AsyncFnMut
...)
We may in the future move AsyncFn*
onto a more general set of LendingFn*
traits; however, there are some concrete technical implementation details that limit our ability to use LendingFn
ergonomically in the compiler today. These have to do with:
These limitations, plus the fact that the underlying trait should have no effect on the user experience of async closures and async Fn
trait bounds, leads us to AsyncFn*
for now. To ensure we can eventually move to these more general traits, the precise AsyncFn*
trait definitions (including the associated types) are left as an implementation detail.
Fn*
traits?We mention above that "regular" callable types can implement AsyncFn*
, but the reverse question exists of "can async closures implement Fn*
too"? The short answer is "when it's valid", i.e. when the coroutine that would have been returned from AsyncFn
/AsyncFnMut
does not actually have any upvars that are "lent" from the parent coroutine-closure.
See the "follow-up: when do..." section below for an elaborated answer. The full answer describes a pretty interesting and hopefully thorough heuristic that is used to ensure that most async closures "just work".
+When async closures are called with AsyncFn
/AsyncFnMut
, they return a coroutine that borrows from the closure. However, when they are called via AsyncFnOnce
, we consume that closure, and cannot return a coroutine that borrows from data that is now dropped.
To work around around this limitation, we synthesize a separate by-move MIR body for calling AsyncFnOnce::call_once
on a coroutine-closure that can be called by-ref.
This body operates identically to the "normal" coroutine returned from calling the coroutine-closure, except for the fact that it has a different set of upvars, since we must move the captures from the parent coroutine-closure into the child coroutine.
+When we want to access the by-move body of the coroutine returned by a coroutine-closure, we can do so via the coroutine_by_move_body_def_id
14 query.
This query synthesizes a new MIR body by copying the MIR body of the coroutine and inserting additional derefs and field projections15 to preserve the semantics of the body.
+ +Since we've synthesized a new def id, this query is also responsible for feeding a ton of other relevant queries for the MIR body. This query is ensure()
d16 during the mir_promoted
query, since it operates on the built mir of the coroutine.
The closure signature inference algorithm for async closures is a bit more complicated than the inference algorithm for "traditional" closures. Like closures, we iterate through all of the clauses that may be relevant (for the expectation type passed in)17.
+To extract a signature, we consider two situations:
+AsyncFnOnce::Output
, which we will use to extract the inputs and output type for the closure. This corresponds to the situation that there was a F: AsyncFn*() -> T
bound18.FnOnce::Output
, which we will use to extract the inputs. For the output, we also try to deduce an output by looking for relevant Future::Output
projection predicates. This corresponds to the situation that there was an F: Fn*() -> T, T: Future<Output = U>
bound.19
+Future
bound, we simply use a fresh infer var for the output. This corresponds to the case where one can pass an async closure to a combinator function like Option::map
.20We support the latter case simply to make it easier for users to simply drop-in async || {}
syntax, even when they're calling an API that was designed before first-class AsyncFn*
traits were available.
We defer21 the computation of a coroutine-closure's "kind" (i.e. its maximum calling mode: AsyncFnOnce
/AsyncFnMut
/AsyncFn
) until the end of typeck. However, since we want to be able to call that coroutine-closure before the end of typeck, we need to come up with the return type of the coroutine-closure before that.
Unlike regular closures, whose return type does not change depending on what Fn*
trait we call it with, coroutine-closures do end up returning different coroutine types depending on the flavor of AsyncFn*
trait used to call it.
Specifically, while the def-id of the returned coroutine does not change, the upvars22 (which are either borrowed or moved from the parent coroutine-closure) and the coroutine-kind23 are dependent on the calling mode.
+ + +We introduce a AsyncFnKindHelper
trait which allows us to defer the question of "does this coroutine-closure support this calling mode"24 via a trait goal, and "what are the tupled upvars of this calling mode"25 via an associated type, which can be computed by appending the input types of the coroutine-closure to either the upvars or the "by ref" upvars computed during upvar analysis.
This seems a bit roundabout and complex, and I admit that it is. But let's think of the "do nothing" alternative -- we could instead mark all AsyncFn*
goals as ambiguous until upvar analysis, at which point we would know exactly what to put into the upvars of the coroutine we return. However, this is actually very detrimental to inference in the program, since it means that programs like this would not be valid:
+#![allow(unused)] +fn main() { +let c = async || -> String { .. }; +let s = c().await; +// ^^^ If we can't project `<{c} as AsyncFn>::call()` to a coroutine, then the `IntoFuture::into_future` call inside of the `.await` stalls, and the type of `s` is left unconstrained as an infer var. +s.as_bytes(); +// ^^^ That means we can't call any methods on the awaited return of a coroutine-closure, like... at all! +} +
So instead, we use this alias (in this case, a projection: AsyncFnKindHelper::Upvars<'env, ...>
) to delay the computation of the tupled upvars and give us something to put in its place, while still allowing us to return a TyKind::Coroutine
(which is a rigid type) and we may successfully confirm the built-in traits we need (in our case, Future
), since the Future
implementation doesn't depend on the upvars at all.
By and large, the upvar analysis for coroutine-closures and their child coroutines proceeds like normal upvar analysis. However, there are several interesting bits that happen to account for async closures' special natures:
+Like async fn, all input arguments are captured. We explicitly force26 all of these inputs to be captured by move so that the future coroutine returned by async closures does not depend on whether the input is used by the body or not, which would impart an interesting semver hazard.
+ +For a coroutine-closure that supports AsyncFn
/AsyncFnMut
, we must also compute the relationship between the captures of the coroutine-closure and its child coroutine. Specifically, the coroutine-closure may move
a upvar into its captures, but the coroutine may only borrow that upvar.
We compute the "coroutine_captures_by_ref_ty
" by looking at all of the child coroutine's captures and comparing them to the corresponding capture of the parent coroutine-closure27. This coroutine_captures_by_ref_ty
ends up being represented as a for<'env> fn() -> captures...
type, with the additional binder lifetime representing the "&self
" lifetime of calling AsyncFn::async_call
or AsyncFnMut::async_call_mut
. We instantiate that binder later when actually calling the methods.
Note that not every by-ref capture from the parent coroutine-closure results in a "lending" borrow. See the Follow-up: When do async closures implement the regular Fn*
traits? section below for more details, since this intimately influences whether or not the coroutine-closure is allowed to implement the Fn*
family of traits.
FnOnce
quirkThere are several situations where the closure upvar analysis ends up inferring upvars for the coroutine-closure's child coroutine that are too relaxed, and end up resulting in borrow-checker errors. This is best illustrated via examples. For example, given:
++#![allow(unused)] +fn main() { +fn force_fnonce<T: async FnOnce()>(t: T) -> T { t } + +let x = String::new(); +let c = force_fnonce(async move || { + println!("{x}"); +}); +} +
x
will be moved into the coroutine-closure, but the coroutine that is returned would only borrow &x
. However, since force_fnonce
forces the coroutine-closure to AsyncFnOnce
, which is not lending, we must force the capture to happen by-move28.
Similarly:
++#![allow(unused)] +fn main() { +let x = String::new(); +let y = String::new(); +let c = async move || { + drop(y); + println!("{x}"); +}; +} +
x
will be moved into the coroutine-closure, but the coroutine that is returned would only borrow &x
. However, since we also capture y
and drop it, the coroutine-closure is forced to be AsyncFnOnce
. We must also force the capture of x
to happen by-move. To determine this situation in particular, since unlike the last example the coroutine-kind's closure-kind has not yet been constrained, we must analyze the body of the coroutine-closure to see if how all of the upvars are used, to determine if they've been used in a way that is "consuming" -- i.e. that would force it to FnOnce
29.
Fn*
traits?Well, first of all, all async closures implement FnOnce
since they can always be called at least once.
For Fn
/FnMut
, the detailed answer involves answering a related question: is the coroutine-closure lending? Because if it is, then it cannot implement the non-lending Fn
/FnMut
traits.
Determining when the coroutine-closure must lend its upvars is implemented in the should_reborrow_from_env_of_parent_coroutine_closure
helper function30. Specifically, this needs to happen in two places:
+#![allow(unused)] +fn main() { +let x = &1i32; // Let's call this lifetime `'1`. +let c = async move || { + println!("{:?}", *x); + // Even though the inner coroutine borrows by ref, we're only capturing `*x`, + // not `x`, so the inner closure is allowed to reborrow the data for `'1`. +}; +} +
+#![allow(unused)] +fn main() { +let mut x = 1i32; +let c = async || { + x = 1; + // The parent borrows `x` for some `&'1 mut i32`. + // However, when we call `c()`, we implicitly autoref for the signature of + // `AsyncFnMut::async_call_mut`. Let's call that lifetime `'call`. Since + // the maximum that `&'call mut &'1 mut i32` can be reborrowed is `&'call mut i32`, + // the inner coroutine should capture w/ the lifetime of the coroutine-closure. +}; +} +
If either of these cases apply, then we should capture the borrow with the lifetime of the parent coroutine-closure's env. Luckily, if this function is not correct, then the program is not unsound, since we still borrowck and validate the choices made from this function -- the only side-effect is that the user may receive unnecessary borrowck errors.
+If a coroutine-closure has a closure-kind of FnOnce
, then its AsyncFnOnce::call_once
and FnOnce::call_once
implementations resolve to the coroutine-closure's body31, and the Future::poll
of the coroutine that gets returned resolves to the body of the child closure.
If a coroutine-closure has a closure-kind of FnMut
/Fn
, then the same applies to AsyncFn
and the corresponding Future
implementation of the coroutine that gets returned.31 However, we use a MIR shim to generate the implementation of AsyncFnOnce::call_once
/FnOnce::call_once
32, and Fn::call
/FnMut::call_mut
instances if they exist33.
This is represented by the ConstructCoroutineInClosureShim
34. The receiver_by_ref
bool will be true if this is the instance of Fn::call
/FnMut::call_mut
.35 The coroutine that all of these instances returns corresponds to the by-move body we will have synthesized by this point.36
It turns out that borrow-checking async closures is pretty straightforward. After adding a new DefiningTy::CoroutineClosure
37 variant, and teaching borrowck how to generate the signature of the coroutine-closure38, borrowck proceeds totally fine.
One thing to note is that we don't borrow-check the synthetic body we make for by-move coroutines, since by construction (and the validity of the by-ref coroutine body it was derived from) it must be valid.
+ + + +The Rust compiler supports building with some dependencies from crates.io
.
+Examples are log
and env_logger
.
In general, +you should avoid adding dependencies to the compiler for several reasons:
+Note that there is no official policy for vetting new dependencies to the compiler. +Decisions are made on a case-by-case basis, during code review.
+The tidy
tool has a list of crates that are allowed. To add a
+dependency that is not already in the compiler, you will need to add it to the list.
rustc
+
+This document explains the state of debugging tools support in the Rust compiler (rustc). +It gives an overview of GDB, LLDB, WinDbg/CDB, +as well as infrastructure around Rust compiler to debug Rust code. +If you want to learn how to debug the Rust compiler itself, +see Debugging the Compiler.
+The material is gathered from the video, +Tom Tromey discusses debugging support in rustc.
+According to Wikipedia
+++A debugger or debugging tool is a computer program that is used to test and debug +other programs (the "target" program).
+
Writing a debugger from scratch for a language requires a lot of work, especially if +debuggers have to be supported on various platforms. GDB and LLDB, however, can be +extended to support debugging a language. This is the path that Rust has chosen. +This document's main goal is to document the said debuggers support in Rust compiler.
+According to the DWARF standard website
+++DWARF is a debugging file format used by many compilers and debuggers to support source level +debugging. It addresses the requirements of a number of procedural languages, +such as C, C++, and Fortran, and is designed to be extensible to other languages. +DWARF is architecture independent and applicable to any processor or operating system. +It is widely used on Unix, Linux and other operating systems, +as well as in stand-alone environments.
+
DWARF reader is a program that consumes the DWARF format and creates debugger compatible output.
+This program may live in the compiler itself. DWARF uses a data structure called
+Debugging Information Entry (DIE) which stores the information as "tags" to denote functions,
+variables etc., e.g., DW_TAG_variable
, DW_TAG_pointer_type
, DW_TAG_subprogram
etc.
+You can also invent your own tags and attributes.
PDB (Program Database) is a file format created by Microsoft that contains debug information. +PDBs can be consumed by debuggers such as WinDbg/CDB and other tools to display debug information. +A PDB contains multiple streams that describe debug information about a specific binary such +as types, symbols, and source files used to compile the given binary. CodeView is another +format which defines the structure of symbol records and type records that appear within +PDB streams.
+To be able to show debug output, we need an expression parser. +This (GDB) expression parser is written in Bison, +and can parse only a subset of Rust expressions. +GDB parser was written from scratch and has no relation to any other parser, +including that of rustc.
+GDB has Rust-like value and type output. It can print values and types in a way +that look like Rust syntax in the output. Or when you print a type as ptype in GDB, +it also looks like Rust source code. Checkout the documentation in the manual for GDB/Rust.
+Expression parser has a couple of extensions in it to facilitate features that you cannot do +with Rust. Some limitations are listed in the manual for GDB/Rust. There is some special +code in the DWARF reader in GDB to support the extensions.
+A couple of examples of DWARF reader support needed are as follows:
+Enum: Needed for support for enum types. +The Rust compiler writes the information about enum into DWARF, +and GDB reads the DWARF to understand where is the tag field, +or if there is a tag field, +or if the tag slot is shared with non-zero optimization etc.
+Dissect trait objects: DWARF extension where the trait object's description in the DWARF
+also points to a stub description of the corresponding vtable which in turn points to the
+concrete type for which this trait object exists. This means that you can do a print *object
+for that trait object, and GDB will understand how to find the correct type of the payload in
+the trait object.
TODO: Figure out if the following should be mentioned in the GDB-Rust document rather than +this guide page so there is no duplication. This is regarding the following comments:
+ +++ +gdb's Rust extensions and limitations are documented in the gdb manual: +https://sourceware.org/gdb/onlinedocs/gdb/Rust.html -- however, this neglects to mention that +gdb convenience variables and registers follow the gdb $ convention, and that the Rust parser +implements the gdb @ extension.
+
++@tromey do you think we should mention this part in the GDB-Rust document rather than this +document so there is no duplication etc.?
+
This expression parser is written in C++. It is a type of Recursive Descent parser. +It implements slightly less of the Rust language than GDB. +LLDB has Rust-like value and type output.
+Microsoft provides Windows Debugging Tools such as the Windows Debugger (WinDbg) and
+the Console Debugger (CDB) which both support debugging programs written in Rust. These
+debuggers parse the debug info for a binary from the PDB
, if available, to construct a
+visualization to serve up in the debugger.
Both WinDbg and CDB support defining and viewing custom visualizations for any given type
+within the debugger using the Natvis framework. The Rust compiler defines a set of Natvis
+files that define custom visualizations for a subset of types in the standard libraries such
+as, std
, core
, and alloc
. These Natvis files are embedded into PDBs
generated by the
+*-pc-windows-msvc
target triples to automatically enable these custom visualizations when
+debugging. This default can be overridden by setting the strip
rustc flag to either debuginfo
+or symbols
.
Rust has support for embedding Natvis files for crates outside of the standard libraries by
+using the #[debugger_visualizer]
attribute.
+For more details on how to embed debugger visualizers,
+please refer to the section on the debugger_visualizer
attribute.
rustc
DWARF is the standard way compilers generate debugging information that debuggers read. +It is the debugging format on macOS and Linux. +It is a multi-language and extensible format, +and is mostly good enough for Rust's purposes. +Hence, the current implementation reuses DWARF's concepts. +This is true even if some of the concepts in DWARF do not align with Rust semantically because, +generally, there can be some kind of mapping between the two.
+We have some DWARF extensions that the Rust compiler emits and the debuggers understand that +are not in the DWARF standard.
+Rust compiler will emit DWARF for a virtual table, and this vtable
object will have a
+DW_AT_containing_type
that points to the real type. This lets debuggers dissect a trait object
+pointer to correctly find the payload. E.g., here's such a DIE, from a test case in the gdb
+repository:
<1><1a9>: Abbrev Number: 3 (DW_TAG_structure_type)
+ <1aa> DW_AT_containing_type: <0x1b4>
+ <1ae> DW_AT_name : (indirect string, offset: 0x23d): vtable
+ <1b2> DW_AT_byte_size : 0
+ <1b3> DW_AT_alignment : 8
+
+The other extension is that the Rust compiler can emit a tagless discriminated union. +See DWARF feature request for this item.
+__0
and debuggers look for a sequence of such names to overcome this limitation.
+For example, in this case the debugger would look at a field via x.__0
instead of x.0
.
+This is resolved via the Rust parser in the debugger so now you can do x.0
.DWARF relies on debuggers to know some information about platform ABI. +Rust does not do that all the time.
+This section is from the talk about certain aspects of development.
+According to Wikipedia, System Integrity Protection is
+++System Integrity Protection (SIP, sometimes referred to as rootless) is a security feature +of Apple's macOS operating system introduced in OS X El Capitan. It comprises a number of +mechanisms that are enforced by the kernel. A centerpiece is the protection of system-owned +files and directories against modifications by processes without a specific "entitlement", +even when executed by the root user or a user with root privileges (sudo).
+
It prevents processes using ptrace
syscall. If a process wants to use ptrace
it has to be
+code signed. The certificate that signs it has to be trusted on your machine.
See Apple developer documentation for System Integrity Protection.
+We may need to sign up with Apple and get the keys to do this signing. Tom has looked into if +Mozilla cannot do this because it is at the maximum number of +keys it is allowed to sign. Tom does not know if Mozilla could get more keys.
+Alternatively, Tom suggests that maybe a Rust legal entity is needed to get the keys via Apple. +This problem is not technical in nature. If we had such a key we could sign GDB as well and +ship that.
+Rust traits are not emitted into DWARF at all. The impact of this is calling a method x.method()
+does not work as is. The reason being that method is implemented by a trait, as opposed
+to a type. That information is not present so finding trait methods is missing.
DWARF has a notion of interface types (possibly added for Java). Tom's idea was to use this +interface type as traits.
+DWARF only deals with concrete names, not the reference types. So, a given implementation of a
+trait for a type would be one of these interfaces (DW_tag_interface
type). Also, the type for
+which it is implemented would describe all the interfaces this type implements. This requires a
+DWARF extension.
Issue on Github: https://github.com/rust-lang/rust/issues/33014
+LLVM has Debug Info (DI) builders. This is the primary thing that Rust calls into. +This is why we need to change LLVM first because that is emitted first and not DWARF directly. +This is a kind of metadata that you construct and hand-off to LLVM. For the Rustc/LLVM hand-off +some LLVM DI builder methods are called to construct representation of a type.
+The steps of this process are as follows:
+LLVM needs changing.
+LLVM does not emit Interface types at all, so this needs to be implemented in the LLVM first.
+Get sign off on LLVM maintainers that this is a good idea.
+Change the DWARF extension.
+Update the debuggers.
+Update DWARF readers, expression evaluators.
+Update Rust compiler.
+Change it to emit this new information.
+A deeply profound question is that how do you actually debug a procedural macro? +What is the location you emit for a macro expansion? Consider some of the following cases -
+RFC: https://github.com/rust-lang/rfcs/pull/2117
+Focus is to let macros decide what to do. This can be achieved by having some kind of attribute +that lets the macro tell the compiler where the line marker should be. This affects where you +set the breakpoints and what happens when you step it.
+Both DWARF and CodeView (PDB) support embedding a cryptographic hash of each source file that +contributed to the associated binary.
+The cryptographic hash can be used by a debugger to verify that the source file matches the +executable. If the source file does not match, the debugger can provide a warning to the user.
+The hash can also be used to prove that a given source file has not been modified since it was +used to compile an executable. Because MD5 and SHA1 both have demonstrated vulnerabilities, +using SHA256 is recommended for this application.
+The Rust compiler stores the hash for each source file in the corresponding SourceFile
in
+the SourceMap
. The hashes of input files to external crates are stored in rlib
metadata.
A default hashing algorithm is set in the target specification. This allows the target to +specify the best hash available, since not all targets support all hash algorithms.
+The hashing algorithm for a target can also be overridden with the -Z source-file-checksum=
+command-line option.
DWARF version 5 supports embedding an MD5 hash to validate the source file version in use. +DWARF 5 - Section 6.2.4.1 opcode DW_LNCT_MD5
+LLVM IR supports MD5 and SHA1 (and SHA256 in LLVM 11+) source file checksums in the DIFile node.
+ +The MSVC compiler supports embedding MD5, SHA1, or SHA256 hashes in the PDB using the /ZH
+compiler option.
Clang always embeds an MD5 checksum, though this does not appear in documentation.
+libiberty
(gcc source tree).TODO: Check the location of the demangler source. #1157
+This is an important idea because debuggers by and large do not try to implement type +inference. You need to be much more explicit when you type into the debugger than your +actual source code. So, you cannot just copy and paste an expression from your source +code to debugger and expect the same answer but this would be nice. This can be helped +by using compiler.
+It is certainly doable but it is a large project. You certainly need a bridge to the +debugger because the debugger alone has access to the memory. Both GDB (gcc) and LLDB (clang) +have this feature. LLDB uses Clang to compile code to JIT and GDB can do the same with GCC.
+Both debuggers expression evaluation implement both a superset and a subset of Rust. +They implement just the expression language, +but they also add some extensions like GDB has convenience variables. +Therefore, if you are taking this route, +then you not only need to do this bridge, +but may have to add some mode to let the compiler understand some extensions.
+ +Span
#[rustc_on_unimplemented(...)]
A lot of effort has been put into making rustc
have great error messages.
+This chapter is about how to emit compile errors and lints from the compiler.
The main parts of a diagnostic error are the following:
+error[E0000]: main error message
+ --> file.rs:LL:CC
+ |
+LL | <code>
+ | -^^^^- secondary label
+ | |
+ | primary label
+ |
+ = note: note without a `Span`, created with `.note`
+note: sub-diagnostic message for `.span_note`
+ --> file.rs:LL:CC
+ |
+LL | more code
+ | ^^^^
+
+error
, warning
, etc.). It indicates the severity of the message.
+(See diagnostic levels)E0308
). It helps
+users get more information about the current error through an extended
+description of the problem in the error code index. Not all diagnostic have a
+code. For example, diagnostics created by lints don't have one.if/else arms have incompatible types
error uses different
+spans depending on whether the arms are all in the same line, if one of
+the arms is empty and if none of those cases applies.The text should be matter of fact and avoid capitalization and periods, unless +multiple sentences are needed:
+error: the fobrulator needs to be krontrificated
+
+When code or an identifier must appear in a message or label, it should be +surrounded with backticks:
+error: the identifier `foo.bar` is invalid
+
+Most errors have an associated error code. Error codes are linked to long-form
+explanations which contains an example of how to trigger the error and in-depth
+details about the error. They may be viewed with the --explain
flag, or via
+the error index.
As a general rule, give an error a code (with an associated explanation) if the +explanation would give more information than the error itself. A lot of the time +it's better to put all the information in the emitted error itself. However, +sometimes that would make the error verbose or there are too many possible +triggers to include useful information for all cases in the error, in which case +it's a good idea to add an explanation.1 +As always, if you are not sure, just ask your reviewer!
+If you decide to add a new error with an associated error code, please read +this section for a guide and important details about the +process.
+This rule of thumb was suggested by @estebank here.
+Some messages are emitted via lints, where the user can control the +level. Most diagnostics are hard-coded such that the user cannot control the +level.
+Usually it is obvious whether a diagnostic should be "fixed" or a lint, but +there are some grey areas.
+Here are a few examples:
+Hard-coded warnings (those using methods like span_warn
) should be avoided
+for normal code, preferring to use lints instead. Some cases, such as warnings
+with CLI flags, will require the use of hard-coded warnings.
See the deny
lint level below for guidelines when to
+use an error-level lint instead of a fixed error.
Error
, Warning
, Note
, and Help
messages start with a lowercase
+letter and do not end with punctuation.--explain
+flag. That said, don't make it so terse that it's hard to understand.rustc_errors::DiagCtxt
's
+span_*
methods or a diagnostic struct's #[primary_span]
to easily do
+this). Also note
other spans that have contributed to the error if the span
+isn't too large.#[rustc_on_unimplemented]
. Use these
+annotations when available!the compiler
, not Rust
or
+rustc
.From RFC 0344, lint names should be consistent, with the following +guidelines:
+The basic rule is: the lint name should make sense when read as "allow
+lint-name" or "allow lint-name items". For example, "allow
+deprecated
items" and "allow dead_code
" makes sense, while "allow
+unsafe_block
" is ungrammatical (should be plural).
Lint names should state the bad thing being checked for, e.g. deprecated
,
+so that #[allow(deprecated)]
(items) reads correctly. Thus ctypes
is not
+an appropriate name; improper_ctypes
is.
Lints that apply to arbitrary items (like the stability lints) should just
+mention what they check for: use deprecated
rather than
+deprecated_items
. This keeps lint names short. (Again, think "allow
+lint-name items".)
If a lint applies to a specific grammatical class, mention that class and
+use the plural form: use unused_variables
rather than unused_variable
.
+This makes #[allow(unused_variables)]
read correctly.
Lints that catch unnecessary, unused, or useless aspects of code should use
+the term unused
, e.g. unused_imports
, unused_typecasts
.
Use snake case in the same way you would for function names.
+Guidelines for different diagnostic levels:
+error
: emitted when the compiler detects a problem that makes it unable to
+compile the program, either because the program is invalid or the programmer
+has decided to make a specific warning
into an error.
warning
: emitted when the compiler detects something odd about a program.
+Care should be taken when adding warnings to avoid warning fatigue, and
+avoid false-positives where there really isn't a problem with the code. Some
+examples of when it is appropriate to issue a warning:
Result
, but otherwise doesn't prevent
+compilation.unsafe
.unused_comparisons
(out of bounds comparisons) or
+bindings_with_variant_name
(the user likely did not intend to create a
+binding in a pattern).dyn
trait
+warning in the 2018 edition. These have a high bar to be added, and should
+only be used in exceptional circumstances. Other stylistic choices should
+either be allow-by-default lints, or part of other tools like Clippy or
+rustfmt.help
: emitted following an error
or warning
to give additional
+information to the user about how to solve their problem. These messages
+often include a suggestion string and rustc_errors::Applicability
+confidence level to guide automated source fixes by tools. See the
+Suggestions section for more details.
The error or warning portion should not suggest how to fix the problem, +only the "help" sub-diagnostic should.
+note
: emitted to given more context and identify additional circumstances
+and parts of the code that caused the warning or error. For example, the
+borrow checker will note any previous conflicting borrows.
help
vs note
: help
should be used to show changes the user can
+possibly make to fix the problem. note
should be used for everything else,
+such as other context, information and facts, online resources to read, etc.
Not to be confused with lint levels, whose guidelines are:
+forbid
: Lints should never default to forbid
.
deny
: Equivalent to error
diagnostic level. Some examples:
warn
: Equivalent to the warning
diagnostic level. See warning
above
+for guidelines.
allow
: Examples of the kinds of lints that should default to allow
:
unsafe_code
lint can be used to prevent usage of unsafe
+code.More information about lint levels can be found in the rustc +book and the reference.
+There are three main ways to find where a given error is emitted:
+grep
for either a sub-part of the error message/label or error code. This
+usually works well and is straightforward, but there are some cases where
+the code emitting the error is removed from the code where the error is
+constructed behind a relatively deep call-stack. Even then, it is a good way
+to get your bearings.
Invoking rustc
with the nightly-only flag -Z treat-err-as-bug=1
+will treat the first error being emitted as an Internal Compiler Error, which
+allows you to get a
+stack trace at the point the error has been emitted. Change the 1
to
+something else if you wish to trigger on a later error.
There are limitations with this approach:
+rustc
.grep
approach.
+In some cases, we buffer multiple errors in order to emit them in order.Invoking rustc
with -Z track-diagnostics
will print error creation
+locations alongside the error.
The regular development practices apply: judicious use of debug!()
statements
+and use of a debugger to trigger break points in order to figure out in what
+order things are happening.
Span
Span
is the primary data structure in rustc
used to represent a
+location in the code being compiled. Span
s are attached to most constructs in
+HIR and MIR, allowing for more informative error reporting.
A Span
can be looked up in a SourceMap
to get a "snippet"
+useful for displaying errors with span_to_snippet
and other
+similar methods on the SourceMap
.
The rustc_errors
crate defines most of the utilities used for
+reporting errors.
Diagnostics can be implemented as types which implement the Diagnostic
+trait. This is preferred for new diagnostics as it enforces a separation
+between diagnostic emitting logic and the main code paths. For less-complex
+diagnostics, the Diagnostic
trait can be derived -- see Diagnostic
+structs. Within the trait implementation, the APIs
+described below can be used as normal.
DiagCtxt
has methods that create and emit errors. These methods
+usually have names like span_err
or struct_span_err
or span_warn
, etc...
+There are lots of them; they emit different types of "errors", such as
+warnings, errors, fatal errors, suggestions, etc.
In general, there are two classes of such methods: ones that emit an error
+directly and ones that allow finer control over what to emit. For example,
+span_err
emits the given error message at the given Span
, but
+struct_span_err
instead returns a
+Diag
.
Most of these methods will accept strings, but it is recommended that typed +identifiers for translatable diagnostics be used for new diagnostics (see +Translation).
+Diag
allows you to add related notes and suggestions to an error
+before emitting it by calling the emit
method. (Failing to either
+emit or cancel a Diag
will result in an ICE.) See the
+docs for more info on what you can do.
// Get a `Diag`. This does _not_ emit an error yet.
+let mut err = sess.dcx.struct_span_err(sp, fluent::example::example_error);
+
+// In some cases, you might need to check if `sp` is generated by a macro to
+// avoid printing weird errors about macro-generated code.
+
+if let Ok(snippet) = sess.source_map().span_to_snippet(sp) {
+ // Use the snippet to generate a suggested fix
+ err.span_suggestion(suggestion_sp, fluent::example::try_qux_suggestion, format!("qux {}", snippet));
+} else {
+ // If we weren't able to generate a snippet, then emit a "help" message
+ // instead of a concrete "suggestion". In practice this is unlikely to be
+ // reached.
+ err.span_help(suggestion_sp, fluent::example::qux_suggestion);
+}
+
+// emit the error
+err.emit();
+
+example-example-error = oh no! this is an error!
+ .try-qux-suggestion = try using a qux here
+ .qux-suggestion = you could use a qux here instead
+
+In addition to telling the user exactly why their code is wrong, it's
+oftentimes furthermore possible to tell them how to fix it. To this end,
+Diag
offers a structured suggestions API, which formats code
+suggestions pleasingly in the terminal, or (when the --error-format json
flag
+is passed) as JSON for consumption by tools like rustfix
.
Not all suggestions should be applied mechanically, they have a degree of
+confidence in the suggested code, from high
+(Applicability::MachineApplicable
) to low (Applicability::MaybeIncorrect
).
+Be conservative when choosing the level. Use the
+span_suggestion
method of Diag
to
+make a suggestion. The last argument provides a hint to tools whether
+the suggestion is mechanically applicable or not.
Suggestions point to one or more spans with corresponding code that will +replace their current content.
+The message that accompanies them should be understandable in the following +contexts:
+help
sub-diagnostic with no content (used for cases where the
+suggestion is obvious from the text, but we still want to let tools to apply
+them))For example, to make our qux
suggestion machine-applicable, we would do:
let mut err = sess.dcx.struct_span_err(sp, fluent::example::message);
+
+if let Ok(snippet) = sess.source_map().span_to_snippet(sp) {
+ err.span_suggestion(
+ suggestion_sp,
+ fluent::example::try_qux_suggestion,
+ format!("qux {}", snippet),
+ Applicability::MachineApplicable,
+ );
+} else {
+ err.span_help(suggestion_sp, fluent::example::qux_suggestion);
+}
+
+err.emit();
+
+This might emit an error like
+$ rustc mycode.rs
+error[E0999]: oh no! this is an error!
+ --> mycode.rs:3:5
+ |
+3 | sad()
+ | ^ help: try using a qux here: `qux sad()`
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0999`.
+
+In some cases, like when the suggestion spans multiple lines or when there are +multiple suggestions, the suggestions are displayed on their own:
+error[E0999]: oh no! this is an error!
+ --> mycode.rs:3:5
+ |
+3 | sad()
+ | ^
+help: try using a qux here:
+ |
+3 | qux sad()
+ | ^^^
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0999`.
+
+The possible values of Applicability
are:
MachineApplicable
: Can be applied mechanically.HasPlaceholders
: Cannot be applied mechanically because it has placeholder
+text in the suggestions. For example: try adding a type: `let x: <type>`
.MaybeIncorrect
: Cannot be applied mechanically because the suggestion may
+or may not be a good one.Unspecified
: Cannot be applied mechanically because we don't know which
+of the above cases it falls into.Suggestions should not be a question. In particular, language like "did you +mean" should be avoided. Sometimes, it's unclear why a particular suggestion +is being made. In these cases, it's better to be upfront about what the +suggestion is.
+Compare "did you mean: Foo
" vs. "there is a struct with a similar name: Foo
".
The message should not contain any phrases like "the following", "as shown", +etc. Use the span to convey what is being talked about.
+The message may contain further instruction such as "to do xyz, use" or "to do +xyz, use abc".
+The message may contain a name of a function, variable, or type, but avoid +whole expressions.
+The compiler linting infrastructure is defined in the rustc_middle::lint
+module.
Different lints will run at different times based on what information the lint +needs to do its job. Some lints get grouped into passes where the lints +within a pass are processed together via a single visitor. Some of the passes +are:
+Pre-expansion pass: Works on AST nodes before macro expansion. This +should generally be avoided.
+keyword_idents
checks for identifiers that will become
+keywords in future editions, but is sensitive to identifiers used in
+macros.Early lint pass: Works on AST nodes after macro expansion and name +resolution, just before AST lowering. These lints are for purely +syntactical lints.
+unused_parens
lint checks for parenthesized-expressions
+in situations where they are not needed, like an if
condition.Late lint pass: Works on HIR nodes, towards the end of analysis (after +borrow checking, etc.). These lints have full type information available. +Most lints are late.
+invalid_value
lint (which checks for obviously invalid
+uninitialized values) is a late lint because it needs type information to
+figure out whether a type allows being left uninitialized.MIR pass: Works on MIR nodes. This isn't quite the same as other passes; +lints that work on MIR nodes have their own methods for running.
+arithmetic_overflow
lint is emitted when it detects a
+constant value that may overflow.Most lints work well via the pass systems, and they have a fairly
+straightforward interface and easy way to integrate (mostly just implementing
+a specific check
function). However, some lints are easier to write when
+they live on a specific code path anywhere in the compiler. For example, the
+unused_mut
lint is implemented in the borrow checker as it requires some
+information and state in the borrow checker.
Some of these inline lints fire before the linting system is ready. Those +lints will be buffered where they are held until later phases of the +compiler when the linting system is ready. See Linting early in the +compiler.
+Lints are managed via the LintStore
and get registered in
+various ways. The following terms refer to the different classes of lints
+generally based on how they are registered.
clippy::
or rustdoc::
.rustc::
scoped tool lints that only run on the
+rustc source tree itself and are defined in the compiler source like a
+regular built-in lint.More information about lint registration can be found in the LintStore +chapter.
+The built-in compiler lints are defined in the rustc_lint
+crate. Lints that need to be implemented in other crates are defined in
+rustc_lint_defs
. You should prefer to place lints in rustc_lint
if
+possible. One benefit is that it is close to the dependency root, so it can be
+much faster to work on.
Every lint is implemented via a struct
that implements the LintPass
trait
+(you can also implement one of the more specific lint pass traits, either
+EarlyLintPass
or LateLintPass
depending on when is best for your lint to run).
+The trait implementation allows you to check certain syntactic constructs
+as the linter walks the AST. You can then choose to emit lints in a
+very similar way to compile errors.
You also declare the metadata of a particular lint via the declare_lint!
+macro. This includes the name, the default level, a short description, and some
+more details.
Note that the lint and the lint pass must be registered with the compiler.
+For example, the following lint checks for uses
+of while true { ... }
and suggests using loop { ... }
instead.
// Declare a lint called `WHILE_TRUE`
+declare_lint! {
+ WHILE_TRUE,
+
+ // warn-by-default
+ Warn,
+
+ // This string is the lint description
+ "suggest using `loop { }` instead of `while true { }`"
+}
+
+// This declares a struct and a lint pass, providing a list of associated lints. The
+// compiler currently doesn't use the associated lints directly (e.g., to not
+// run the pass or otherwise check that the pass emits the appropriate set of
+// lints). However, it's good to be accurate here as it's possible that we're
+// going to register the lints via the get_lints method on our lint pass (that
+// this macro generates).
+declare_lint_pass!(WhileTrue => [WHILE_TRUE]);
+
+// Helper function for `WhileTrue` lint.
+// Traverse through any amount of parenthesis and return the first non-parens expression.
+fn pierce_parens(mut expr: &ast::Expr) -> &ast::Expr {
+ while let ast::ExprKind::Paren(sub) = &expr.kind {
+ expr = sub;
+ }
+ expr
+}
+
+// `EarlyLintPass` has lots of methods. We only override the definition of
+// `check_expr` for this lint because that's all we need, but you could
+// override other methods for your own lint. See the rustc docs for a full
+// list of methods.
+impl EarlyLintPass for WhileTrue {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
+ if let ast::ExprKind::While(cond, ..) = &e.kind
+ && let ast::ExprKind::Lit(ref lit) = pierce_parens(cond).kind
+ && let ast::LitKind::Bool(true) = lit.kind
+ && !lit.span.from_expansion()
+ {
+ let condition_span = cx.sess.source_map().guess_head_span(e.span);
+ cx.struct_span_lint(WHILE_TRUE, condition_span, |lint| {
+ lint.build(fluent::example::use_loop)
+ .span_suggestion_short(
+ condition_span,
+ fluent::example::suggestion,
+ "loop".to_owned(),
+ Applicability::MachineApplicable,
+ )
+ .emit();
+ })
+ }
+ }
+}
+
+example-use-loop = denote infinite loops with `loop {"{"} ... {"}"}`
+ .suggestion = use `loop`
+
+Sometimes we want to change the behavior of a lint in a new edition. To do this,
+we just add the transition to our invocation of declare_lint!
:
declare_lint! {
+ pub ANONYMOUS_PARAMETERS,
+ Allow,
+ "detects anonymous parameters",
+ Edition::Edition2018 => Warn,
+}
+
+This makes the ANONYMOUS_PARAMETERS
lint allow-by-default in the 2015 edition
+but warn-by-default in the 2018 edition.
See Edition-specific lints for more information.
+Lints belonging to a feature should only be usable if the feature is enabled in the +crate. To support this, lint declarations can contain a feature gate like so:
+declare_lint! {
+ pub SOME_LINT_NAME,
+ Warn,
+ "a new and useful, but feature gated lint",
+ @feature_gate = sym::feature_name;
+}
+
+The use of the term future-incompatible
within the compiler has a slightly
+broader meaning than what rustc exposes to users of the compiler.
Inside rustc, future-incompatible lints are for signalling to the user that code they have +written may not compile in the future. In general, future-incompatible code +exists for two reasons:
+rust_2021_compatibility
)
+that are used to lint against code that will break if the user updates the crate's edition.
+See migration lints for more details.A future-incompatible lint should be declared with the @future_incompatible
+additional "field":
declare_lint! {
+ pub ANONYMOUS_PARAMETERS,
+ Allow,
+ "detects anonymous parameters",
+ @future_incompatible = FutureIncompatibleInfo {
+ reference: "issue #41686 <https://github.com/rust-lang/rust/issues/41686>",
+ reason: FutureIncompatibilityReason::EditionError(Edition::Edition2018),
+ };
+}
+
+Notice the reason
field which describes why the future incompatible change is happening.
+This will change the diagnostic message the user receives as well as determine which
+lint groups the lint is added to. In the example above, the lint is an "edition lint"
+(since its "reason" is EditionError
), signifying to the user that the use of anonymous
+parameters will no longer compile in Rust 2018 and beyond.
Inside LintStore::register_lints, lints with future_incompatible
+fields get placed into either edition-based lint groups (if their reason
is tied to
+an edition) or into the future_incompatibility
lint group.
If you need a combination of options that's not supported by the
+declare_lint!
macro, you can always change the declare_lint!
macro
+to support this.
If it is determined that a lint is either improperly named or no longer needed,
+the lint must be registered for renaming or removal, which will trigger a warning if a user tries
+to use the old lint name. To declare a rename/remove, add a line with
+store.register_renamed
or store.register_removed
to the code of the
+rustc_lint::register_builtins
function.
store.register_renamed("single_use_lifetime", "single_use_lifetimes");
+
+Lints can be turned on in groups. These groups are declared in the
+register_builtins
function in rustc_lint::lib
. The
+add_lint_group!
macro is used to declare a new group.
For example,
+add_lint_group!(sess,
+ "nonstandard_style",
+ NON_CAMEL_CASE_TYPES,
+ NON_SNAKE_CASE,
+ NON_UPPER_CASE_GLOBALS);
+
+This defines the nonstandard_style
group which turns on the listed lints. A
+user can turn on these lints with a !#[warn(nonstandard_style)]
attribute in
+the source code, or by passing -W nonstandard-style
on the command line.
Some lint groups are created automatically in LintStore::register_lints
. For instance,
+any lint declared with FutureIncompatibleInfo
where the reason is
+FutureIncompatibilityReason::FutureReleaseError
(the default when
+@future_incompatible
is used in declare_lint!
), will be added to
+the future_incompatible
lint group. Editions also have their own lint groups
+(e.g., rust_2021_compatibility
) automatically generated for any lints signaling
+future-incompatible code that will break in the specified edition.
On occasion, you may need to define a lint that runs before the linting system +has been initialized (e.g. during parsing or macro expansion). This is +problematic because we need to have computed lint levels to know whether we +should emit a warning or an error or nothing at all.
+To solve this problem, we buffer the lints until the linting system is
+processed. Session
and ParseSess
both have
+buffer_lint
methods that allow you to buffer a lint for later. The linting
+system automatically takes care of handling buffered lints later.
Thus, to define a lint that runs early in the compilation, one defines a lint
+like normal but invokes the lint with buffer_lint
.
The parser (rustc_ast
) is interesting in that it cannot have dependencies on
+any of the other rustc*
crates. In particular, it cannot depend on
+rustc_middle::lint
or rustc_lint
, where all of the compiler linting
+infrastructure is defined. That's troublesome!
To solve this, rustc_ast
defines its own buffered lint type, which
+ParseSess::buffer_lint
uses. After macro expansion, these buffered lints are
+then dumped into the Session::buffered_lints
used by the rest of the compiler.
The compiler accepts an --error-format json
flag to output
+diagnostics as JSON objects (for the benefit of tools such as cargo fix
). It looks like this:
$ rustc json_error_demo.rs --error-format json
+{"message":"cannot add `&str` to `{integer}`","code":{"code":"E0277","explanation":"\nYou tried to use a type which doesn't implement some trait in a place which\nexpected that trait. Erroneous code example:\n\n```compile_fail,E0277\n// here we declare the Foo trait with a bar method\ntrait Foo {\n fn bar(&self);\n}\n\n// we now declare a function which takes an object implementing the Foo trait\nfn some_func<T: Foo>(foo: T) {\n foo.bar();\n}\n\nfn main() {\n // we now call the method with the i32 type, which doesn't implement\n // the Foo trait\n some_func(5i32); // error: the trait bound `i32 : Foo` is not satisfied\n}\n```\n\nIn order to fix this error, verify that the type you're using does implement\nthe trait. Example:\n\n```\ntrait Foo {\n fn bar(&self);\n}\n\nfn some_func<T: Foo>(foo: T) {\n foo.bar(); // we can now use this method since i32 implements the\n // Foo trait\n}\n\n// we implement the trait on the i32 type\nimpl Foo for i32 {\n fn bar(&self) {}\n}\n\nfn main() {\n some_func(5i32); // ok!\n}\n```\n\nOr in a generic context, an erroneous code example would look like:\n\n```compile_fail,E0277\nfn some_func<T>(foo: T) {\n println!(\"{:?}\", foo); // error: the trait `core::fmt::Debug` is not\n // implemented for the type `T`\n}\n\nfn main() {\n // We now call the method with the i32 type,\n // which *does* implement the Debug trait.\n some_func(5i32);\n}\n```\n\nNote that the error here is in the definition of the generic function: Although\nwe only call it with a parameter that does implement `Debug`, the compiler\nstill rejects the function: It must work with all possible input types. In\norder to make this example compile, we need to restrict the generic type we're\naccepting:\n\n```\nuse std::fmt;\n\n// Restrict the input type to types that implement Debug.\nfn some_func<T: fmt::Debug>(foo: T) {\n println!(\"{:?}\", foo);\n}\n\nfn main() {\n // Calling the method is still fine, as i32 implements Debug.\n some_func(5i32);\n\n // This would fail to compile now:\n // struct WithoutDebug;\n // some_func(WithoutDebug);\n}\n```\n\nRust only looks at the signature of the called function, as such it must\nalready specify all requirements that will be used for every type parameter.\n"},"level":"error","spans":[{"file_name":"json_error_demo.rs","byte_start":50,"byte_end":51,"line_start":4,"line_end":4,"column_start":7,"column_end":8,"is_primary":true,"text":[{"text":" a + b","highlight_start":7,"highlight_end":8}],"label":"no implementation for `{integer} + &str`","suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"the trait `std::ops::Add<&str>` is not implemented for `{integer}`","code":null,"level":"help","spans":[],"children":[],"rendered":null}],"rendered":"error[E0277]: cannot add `&str` to `{integer}`\n --> json_error_demo.rs:4:7\n |\n4 | a + b\n | ^ no implementation for `{integer} + &str`\n |\n = help: the trait `std::ops::Add<&str>` is not implemented for `{integer}`\n\n"}
+{"message":"aborting due to previous error","code":null,"level":"error","spans":[],"children":[],"rendered":"error: aborting due to previous error\n\n"}
+{"message":"For more information about this error, try `rustc --explain E0277`.","code":null,"level":"","spans":[],"children":[],"rendered":"For more information about this error, try `rustc --explain E0277`.\n"}
+
+Note that the output is a series of lines, each of which is a JSON
+object, but the series of lines taken together is, unfortunately, not
+valid JSON, thwarting tools and tricks (such as piping to python3 -m json.tool
)
+that require such. (One speculates that this was intentional for LSP
+performance purposes, so that each line/object can be sent as
+it is flushed?)
Also note the "rendered" field, which contains the "human" output as a +string; this was introduced so that UI tests could both make use of +the structured JSON and see the "human" output (well, sans colors) +without having to compile everything twice.
+The "human" readable and the json format emitter can be found under
+rustc_errors
, both were moved from the rustc_ast
crate to the
+rustc_errors crate.
The JSON emitter defines its own Diagnostic
+struct
+(and sub-structs) for the JSON serialization. Don't confuse this with
+errors::Diag
!
#[rustc_on_unimplemented(...)]
The #[rustc_on_unimplemented]
attribute allows trait definitions to add specialized
+notes to error messages when an implementation was expected but not found.
+You can refer to the trait's generic arguments by name and to the resolved type using Self
.
For example:
+#![feature(rustc_attrs)]
+
+#[rustc_on_unimplemented="an iterator over elements of type `{A}` \
+ cannot be built from a collection of type `{Self}`"]
+trait MyIterator<A> {
+ fn next(&mut self) -> A;
+}
+
+fn iterate_chars<I: MyIterator<char>>(i: I) {
+ // ...
+}
+
+fn main() {
+ iterate_chars(&[1, 2, 3][..]);
+}
+
+When the user compiles this, they will see the following;
+error[E0277]: the trait bound `&[{integer}]: MyIterator<char>` is not satisfied
+ --> <anon>:14:5
+ |
+14 | iterate_chars(&[1, 2, 3][..]);
+ | ^^^^^^^^^^^^^ an iterator over elements of type `char` cannot be built from a collection of type `&[{integer}]`
+ |
+ = help: the trait `MyIterator<char>` is not implemented for `&[{integer}]`
+ = note: required by `iterate_chars`
+
+rustc_on_unimplemented
also supports advanced filtering for better targeting
+of messages, as well as modifying specific parts of the error message. You
+target the text of:
message
)label
)note
)For example, the following attribute
+#[rustc_on_unimplemented(
+ message="message",
+ label="label",
+ note="note"
+)]
+trait MyIterator<A> {
+ fn next(&mut self) -> A;
+}
+
+Would generate the following output:
+error[E0277]: message
+ --> <anon>:14:5
+ |
+14 | iterate_chars(&[1, 2, 3][..]);
+ | ^^^^^^^^^^^^^ label
+ |
+ = note: note
+ = help: the trait `MyIterator<char>` is not implemented for `&[{integer}]`
+ = note: required by `iterate_chars`
+
+To allow more targeted error messages, it is possible to filter the
+application of these fields based on a variety of attributes when using
+on
:
crate_local
: whether the code causing the trait bound to not be
+fulfilled is part of the user's crate. This is used to avoid suggesting
+code changes that would require modifying a dependency.Rhs="i32"
, except for
+Self
._Self
: to filter only on a particular calculated trait resolution, like
+Self="std::iter::Iterator<char>"
. This is needed because Self
is a
+keyword which cannot appear in attributes.direct
: user-specified rather than derived obligation.from_method
: usable both as boolean (whether the flag is present, like
+crate_local
) or matching against a particular method. Currently used
+for try
.from_desugaring
: usable both as boolean (whether the flag is present)
+or matching against a particular desugaring. The desugaring is identified
+with its variant name in the DesugaringKind
enum.For example, the Iterator
trait can be annotated in the following way:
#[rustc_on_unimplemented(
+ on(
+ _Self="&str",
+ note="call `.chars()` or `.as_bytes()` on `{Self}`"
+ ),
+ message="`{Self}` is not an iterator",
+ label="`{Self}` is not an iterator",
+ note="maybe try calling `.iter()` or a similar method"
+)]
+pub trait Iterator {}
+
+Which would produce the following outputs:
+error[E0277]: `Foo` is not an iterator
+ --> src/main.rs:4:16
+ |
+4 | for foo in Foo {}
+ | ^^^ `Foo` is not an iterator
+ |
+ = note: maybe try calling `.iter()` or a similar method
+ = help: the trait `std::iter::Iterator` is not implemented for `Foo`
+ = note: required by `std::iter::IntoIterator::into_iter`
+
+error[E0277]: `&str` is not an iterator
+ --> src/main.rs:5:16
+ |
+5 | for foo in "" {}
+ | ^^ `&str` is not an iterator
+ |
+ = note: call `.chars()` or `.bytes() on `&str`
+ = help: the trait `std::iter::Iterator` is not implemented for `&str`
+ = note: required by `std::iter::IntoIterator::into_iter`
+
+If you need to filter on multiple attributes, you can use all
, any
or
+not
in the following way:
#[rustc_on_unimplemented(
+ on(
+ all(_Self="&str", T="std::string::String"),
+ note="you can coerce a `{T}` into a `{Self}` by writing `&*variable`"
+ )
+)]
+pub trait From<T>: Sized { /* ... */ }
+
+
+ Redirecting to... error-codes.html.
+ + diff --git a/diagnostics/diagnostic-items.html b/diagnostics/diagnostic-items.html new file mode 100644 index 000000000..69fc39426 --- /dev/null +++ b/diagnostics/diagnostic-items.html @@ -0,0 +1,340 @@ + + + + + +While writing lints it's common to check for specific types, traits and
+functions. This raises the question on how to check for these. Types can be
+checked by their complete type path. However, this requires hard coding paths
+and can lead to misclassifications in some edge cases. To counteract this,
+rustc has introduced diagnostic items that are used to identify types via
+Symbol
s.
Diagnostic items are added to items inside rustc
/std
/core
/alloc
with the
+rustc_diagnostic_item
attribute. The item for a specific type can be found by
+opening the source code in the documentation and looking for this attribute.
+Note that it's often added with the cfg_attr
attribute to avoid compilation
+errors during tests. A definition often looks like this:
// This is the diagnostic item for this type vvvvvvv
+#[cfg_attr(not(test), rustc_diagnostic_item = "Penguin")]
+struct Penguin;
+
+Diagnostic items are usually only added to traits, +types, +and standalone functions. +If the goal is to check for an associated type or method, +please use the diagnostic item of the item and reference +Using Diagnostic Items.
+A new diagnostic item can be added with these two steps:
+Find the target item inside the Rust repo. Now add the diagnostic item as a
+string via the rustc_diagnostic_item
attribute. This can sometimes cause
+compilation errors while running tests. These errors can be avoided by using
+the cfg_attr
attribute with the not(test)
condition (it's fine adding
+then for all rustc_diagnostic_item
attributes as a preventive manner). At
+the end, it should look like this:
// This will be the new diagnostic item vvv
+#[cfg_attr(not(test), rustc_diagnostic_item = "Cat")]
+struct Cat;
+
+For the naming conventions of diagnostic items, please refer to +Naming Conventions.
+Diagnostic items in code are accessed via symbols in
+rustc_span::symbol::sym
.
+To add your newly-created diagnostic item,
+simply open the module file,
+and add the name (In this case Cat
) at the correct point in the list.
Now you can create a pull request with your changes. :tada:
+++NOTE: +When using diagnostic items in other projects like Clippy, +it might take some time until the repos get synchronized.
+
Diagnostic items don't have a naming convention yet. +Following are some guidelines that should be used in future, +but might differ from existing names:
+Iterator
and HashMap
)Writer
,
+it's good to choose a more precise name,
+maybe by adding the module to it
+(Example: IoWriter
)std::mem::swap()
should be named using
+snake_case
with one important (export) module as a prefix
+(Examples: mem_swap
and cmp_max
)In rustc, diagnostic items are looked up via Symbol
s from inside the
+rustc_span::symbol::sym
module. These can then be mapped to DefId
s
+using TyCtxt::get_diagnostic_item()
or checked if they match a DefId
+using TyCtxt::is_diagnostic_item()
. When mapping from a diagnostic item to
+a DefId
, the method will return a Option<DefId>
. This can be None
if
+either the symbol isn't a diagnostic item or the type is not registered, for
+instance when compiling with #[no_std]
.
+All the following examples are based on DefId
s and their usage.
+#![allow(unused)] +fn main() { +use rustc_span::symbol::sym; + +/// This example checks if the given type (`ty`) has the type `HashMap` using +/// `TyCtxt::is_diagnostic_item()` +fn example_1(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { + match ty.kind() { + ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(sym::HashMap, adt.did()), + _ => false, + } +} +} +
+#![allow(unused)] +fn main() { +/// This example checks if a given [`DefId`] from a method is part of a trait +/// implementation defined by a diagnostic item. +fn is_diag_trait_item( + cx: &LateContext<'_>, + def_id: DefId, + diag_item: Symbol +) -> bool { + if let Some(trait_did) = cx.tcx.trait_of_item(def_id) { + return cx.tcx.is_diagnostic_item(diag_item, trait_did); + } + false +} +} +
Associated types of diagnostic items can be accessed indirectly by first
+getting the DefId
of the trait and then calling
+TyCtxt::associated_items()
. This returns an AssocItems
object which can
+be used for further checks. Checkout
+clippy_utils::ty::get_iterator_item_ty()
for an example usage of this.
Clippy tries to use diagnostic items where possible and has developed some +wrapper and utility functions. Please also refer to its documentation when +using diagnostic items in Clippy. (See Common tools for writing +lints.)
+These are probably only interesting to people +who really want to take a deep dive into the topic :)
+rustc has three diagnostic traits that can be used to create diagnostics:
+Diagnostic
, LintDiagnostic
, and Subdiagnostic
. For simple diagnostics,
+instead of using the Diag
API to create and emit diagnostics,
+derived impls can be used. They are only suitable for simple diagnostics that
+don't require much logic in deciding whether or not to add additional
+subdiagnostics.
Such diagnostic can be translated into +different languages and each has a slug that uniquely identifies the +diagnostic.
+#[derive(Diagnostic)]
and #[derive(LintDiagnostic)]
Consider the definition of the "field already declared" diagnostic +shown below:
+#[derive(Diagnostic)]
+#[diag(hir_analysis_field_already_declared, code = E0124)]
+pub struct FieldAlreadyDeclared {
+ pub field_name: Ident,
+ #[primary_span]
+ #[label]
+ pub span: Span,
+ #[label(previous_decl_label)]
+ pub prev_span: Span,
+}
+
+Diagnostic
can only be derived on structs and enums.
+Attributes that are placed on the type for structs are placed on each
+variants for enums (or vice versa). Each Diagnostic
has to have one
+attribute, #[diag(...)]
, applied to the struct or each enum variant.
If an error has an error code (e.g. "E0624"), then that can be specified using
+the code
sub-attribute. Specifying a code
isn't mandatory, but if you are
+porting a diagnostic that uses Diag
to use Diagnostic
+then you should keep the code if there was one.
#[diag(..)]
must provide a slug as the first positional argument (a path to an
+item in rustc_errors::fluent::*
). A slug uniquely identifies the diagnostic
+and is also how the compiler knows what error message to emit (in the default
+locale of the compiler, or in the locale requested by the user). See
+translation documentation to learn more about how
+translatable error messages are written and how slug items are generated.
In our example, the Fluent message for the "field already declared" diagnostic +looks like this:
+hir_analysis_field_already_declared =
+ field `{$field_name}` is already declared
+ .label = field already declared
+ .previous_decl_label = `{$field_name}` first declared here
+
+hir_analysis_field_already_declared
is the slug from our example and is followed
+by the diagnostic message.
Every field of the Diagnostic
which does not have an annotation is
+available in Fluent messages as a variable, like field_name
in the example
+above. Fields can be annotated #[skip_arg]
if this is undesired.
Using the #[primary_span]
attribute on a field (that has type Span
)
+indicates the primary span of the diagnostic which will have the main message
+of the diagnostic.
Diagnostics are more than just their primary message, they often include
+labels, notes, help messages and suggestions, all of which can also be
+specified on a Diagnostic
.
#[label]
, #[help]
, #[warning]
and #[note]
can all be applied to fields which have the
+type Span
. Applying any of these attributes will create the corresponding
+subdiagnostic with that Span
. These attributes will look for their
+diagnostic message in a Fluent attribute attached to the primary Fluent
+message. In our example, #[label]
will look for
+hir_analysis_field_already_declared.label
(which has the message "field already
+declared"). If there is more than one subdiagnostic of the same type, then
+these attributes can also take a value that is the attribute name to look for
+(e.g. previous_decl_label
in our example).
Other types have special behavior when used in a Diagnostic
derive:
Option<T>
will only emit a
+subdiagnostic if the option is Some(..)
.Vec<T>
will be repeated for each element of the
+vector.#[help]
, #[warning]
and #[note]
can also be applied to the struct itself, in which case
+they work exactly like when applied to fields except the subdiagnostic won't
+have a Span
. These attributes can also be applied to fields of type ()
for
+the same effect, which when combined with the Option
type can be used to
+represent optional #[note]
/#[help]
/#[warning]
subdiagnostics.
Suggestions can be emitted using one of four field attributes:
+#[suggestion(slug, code = "...", applicability = "...")]
#[suggestion_hidden(slug, code = "...", applicability = "...")]
#[suggestion_short(slug, code = "...", applicability = "...")]
#[suggestion_verbose(slug, code = "...", applicability = "...")]
Suggestions must be applied on either a Span
field or a (Span, MachineApplicability)
field. Similarly to other field attributes, the slug
+specifies the Fluent attribute with the message and defaults to the equivalent
+of .suggestion
. code
specifies the code that should be suggested as a
+replacement and is a format string (e.g. {field_name}
would be replaced by
+the value of the field_name
field of the struct), not a Fluent identifier.
+applicability
can be used to specify the applicability in the attribute, it
+cannot be used when the field's type contains an Applicability
.
In the end, the Diagnostic
derive will generate an implementation of
+Diagnostic
that looks like the following:
impl<'a, G: EmissionGuarantee> Diagnostic<'a> for FieldAlreadyDeclared {
+ fn into_diag(self, dcx: &'a DiagCtxt, level: Level) -> Diag<'a, G> {
+ let mut diag = Diag::new(dcx, level, fluent::hir_analysis_field_already_declared);
+ diag.set_span(self.span);
+ diag.span_label(
+ self.span,
+ fluent::hir_analysis_label
+ );
+ diag.span_label(
+ self.prev_span,
+ fluent::hir_analysis_previous_decl_label
+ );
+ diag
+ }
+}
+
+Now that we've defined our diagnostic, how do we use it? It's quite
+straightforward, just create an instance of the struct and pass it to
+emit_err
(or emit_warning
):
tcx.dcx().emit_err(FieldAlreadyDeclared {
+ field_name: f.ident,
+ span: f.span,
+ prev_span,
+});
+
+#[derive(Diagnostic)]
and #[derive(LintDiagnostic)]
support the
+following attributes:
#[diag(slug, code = "...")]
+rustc_errors::fluent
, e.g.
+rustc_errors::fluent::hir_analysis_field_already_declared
+(rustc_errors::fluent
is implicit in the attribute, so just
+hir_analysis_field_already_declared
).code = "..."
(Optional)
+#[note]
or #[note(slug)]
(Optional)
+Span
, Option<()>
or ()
.rustc_errors::fluent
for the note's
+message.
+.note
.Span
field, creates a spanned note.#[help]
or #[help(slug)]
(Optional)
+Span
, Option<()>
or ()
.rustc_errors::fluent
for the note's
+message.
+.help
.Span
field, creates a spanned help.#[label]
or #[label(slug)]
(Optional)
+Span
fields.rustc_errors::fluent
for the note's
+message.
+.label
.#[warning]
or #[warning(slug)]
(Optional)
+Span
, Option<()>
or ()
.rustc_errors::fluent
for the note's
+message.
+.warn
.#[suggestion{,_hidden,_short,_verbose}(slug, code = "...", applicability = "...")]
+(Optional)
+(Span, MachineApplicability)
or Span
fields.rustc_errors::fluent
, e.g.
+rustc_errors::fluent::hir_analysis_field_already_declared
+(rustc_errors::fluent
is implicit in the attribute, so just
+hir_analysis_field_already_declared
). Fluent attributes for all messages
+exist as top-level items in that module (so hir_analysis_message.attr
is just
+attr
).rustc_errors::fluent::_subdiag::suggestion
(or.suggestion
in Fluent).code = "..."
/code("...", ...)
(Mandatory)
+applicability = "..."
(Optional)
+machine-applicable
, maybe-incorrect
,
+has-placeholders
or unspecified
.#[subdiagnostic]
+Subdiagnostic
(from
+#[derive(Subdiagnostic)]
).#[primary_span]
(Optional)
+Span
fields on Subdiagnostic
s. Not used for LintDiagnostic
s.#[skip_arg]
(Optional)
+#[derive(Subdiagnostic)]
It is common in the compiler to write a function that conditionally adds a
+specific subdiagnostic to an error if it is applicable. Oftentimes these
+subdiagnostics could be represented using a diagnostic struct even if the
+overall diagnostic could not. In this circumstance, the Subdiagnostic
+derive can be used to represent a partial diagnostic (e.g a note, label, help or
+suggestion) as a struct.
Consider the definition of the "expected return type" label +shown below:
++#![allow(unused)] +fn main() { +#[derive(Subdiagnostic)] +pub enum ExpectedReturnTypeLabel<'tcx> { + #[label(hir_analysis_expected_default_return_type)] + Unit { + #[primary_span] + span: Span, + }, + #[label(hir_analysis_expected_return_type)] + Other { + #[primary_span] + span: Span, + expected: Ty<'tcx>, + }, +} +} +
Like Diagnostic
, Subdiagnostic
can be derived for structs or
+enums. Attributes that are placed on the type for structs are placed on each
+variants for enums (or vice versa). Each Subdiagnostic
should have one
+attribute applied to the struct or each variant, one of:
#[label(..)]
for defining a label#[note(..)]
for defining a note#[help(..)]
for defining a help#[warning(..)]
for defining a warning#[suggestion{,_hidden,_short,_verbose}(..)]
for defining a suggestionAll of the above must provide a slug as the first positional argument (a path
+to an item in rustc_errors::fluent::*
). A slug uniquely identifies the
+diagnostic and is also how the compiler knows what error message to emit (in
+the default locale of the compiler, or in the locale requested by the user).
+See translation documentation to learn more about how
+translatable error messages are written and how slug items are generated.
In our example, the Fluent message for the "expected return type" label +looks like this:
+hir_analysis_expected_default_return_type = expected `()` because of default return type
+
+hir_analysis_expected_return_type = expected `{$expected}` because of return type
+
+Using the #[primary_span]
attribute on a field (with type Span
) will denote
+the primary span of the subdiagnostic. A primary span is only necessary for a
+label or suggestion, which can not be spanless.
Every field of the type/variant which does not have an annotation is available
+in Fluent messages as a variable. Fields can be annotated #[skip_arg]
if this
+is undesired.
Like Diagnostic
, Subdiagnostic
supports Option<T>
and
+Vec<T>
fields.
Suggestions can be emitted using one of four attributes on the type/variant:
+#[suggestion(..., code = "...", applicability = "...")]
#[suggestion_hidden(..., code = "...", applicability = "...")]
#[suggestion_short(..., code = "...", applicability = "...")]
#[suggestion_verbose(..., code = "...", applicability = "...")]
Suggestions require #[primary_span]
be set on a field and can have the
+following sub-attributes:
rustc_errors::fluent
corresponding to the Fluent attribute with the message
+and defaults to the equivalent of .suggestion
.code
specifies the code that should be suggested as a replacement and is a
+format string (e.g. {field_name}
would be replaced by the value of the
+field_name
field of the struct), not a Fluent identifier.applicability
can be used to specify the applicability in the attribute, it
+cannot be used when the field's type contains an Applicability
.Applicabilities can also be specified as a field (of type Applicability
)
+using the #[applicability]
attribute.
In the end, the Subdiagnostic
derive will generate an implementation
+of Subdiagnostic
that looks like the following:
+#![allow(unused)] +fn main() { +impl<'tcx> Subdiagnostic for ExpectedReturnTypeLabel<'tcx> { + fn add_to_diag(self, diag: &mut rustc_errors::Diagnostic) { + use rustc_errors::{Applicability, IntoDiagArg}; + match self { + ExpectedReturnTypeLabel::Unit { span } => { + diag.span_label(span, rustc_errors::fluent::hir_analysis_expected_default_return_type) + } + ExpectedReturnTypeLabel::Other { span, expected } => { + diag.set_arg("expected", expected); + diag.span_label(span, rustc_errors::fluent::hir_analysis_expected_return_type) + } + } + } +} +} +
Once defined, a subdiagnostic can be used by passing it to the subdiagnostic
+function (example and example) on a
+diagnostic or by assigning it to a #[subdiagnostic]
-annotated field of a
+diagnostic struct.
#[derive(Subdiagnostic)]
supports the following attributes:
#[label(slug)]
, #[help(slug)]
, #[warning(slug)]
or #[note(slug)]
+rustc_errors::fluent
, e.g.
+rustc_errors::fluent::hir_analysis_field_already_declared
+(rustc_errors::fluent
is implicit in the attribute, so just
+hir_analysis_field_already_declared
).#[suggestion{,_hidden,_short,_verbose}(slug, code = "...", applicability = "...")]
+rustc_errors::fluent
, e.g.
+rustc_errors::fluent::hir_analysis_field_already_declared
+(rustc_errors::fluent
is implicit in the attribute, so just
+hir_analysis::field_already_declared
). Fluent attributes for all messages
+exist as top-level items in that module (so hir_analysis_message.attr
is just
+hir_analysis::attr
).rustc_errors::fluent::_subdiag::suggestion
(or.suggestion
in Fluent).code = "..."
/code("...", ...)
(Mandatory)
+applicability = "..."
(Optional)
+#[applicability]
on a field.machine-applicable
maybe-incorrect
has-placeholders
unspecified
#[multipart_suggestion{,_hidden,_short,_verbose}(slug, applicability = "...")]
+#[suggestion]
applicability = "..."
(Optional): see #[suggestion]
#[primary_span]
(Mandatory for labels and suggestions; optional otherwise; not applicable
+to multipart suggestions)
+Span
fields.#[suggestion_part(code = "...")]
(Mandatory; only applicable to multipart suggestions)
+Span
fields.code = "..."
(Mandatory)
+#[applicability]
(Optional; only applicable to (simple and multipart) suggestions)
+Applicability
fields.#[skip_arg]
(Optional)
+We generally try to assign each error message a unique code like E0123
. These
+codes are defined in the compiler in the diagnostics.rs
files found in each
+crate, which basically consist of macros. All error codes have an associated
+explanation: new error codes must include them. Note that not all historical
+(no longer emitted) error codes have explanations.
The explanations are written in Markdown (see the CommonMark Spec for
+specifics around syntax), and all of them are linked in the rustc_error_codes
+crate. Please read RFC 1567 for details on how to format and write long error
+codes. As of February 2023, there is an
+effort1 to replace this largely outdated RFC with a new more
+flexible standard.
Error explanations should expand on the error message and provide details about +why the error occurs. It is not helpful for users to copy-paste a quick fix; +explanations should help users understand why their code cannot be accepted by +the compiler. Rust prides itself on helpful error messages and long-form +explanations are no exception. However, before error explanations are +overhauled1 it is a bit open as to how exactly they should be +written, as always: ask your reviewer or ask around on the Rust Discord or Zulip.
+See the draft RFC here.
+Error codes are stored in compiler/rustc_error_codes
.
To create a new error, you first need to find the next available
+code. You can find it with tidy
:
./x test tidy
+
+This will invoke the tidy script, which generally checks that your code obeys
+our coding conventions. Some of these jobs check error codes and ensure that
+there aren't duplicates, etc (the tidy check is defined in
+src/tools/tidy/src/error_codes.rs
). Once it is finished with that, tidy will
+print out the highest used error code:
...
+tidy check
+Found 505 error codes
+Highest error code: `E0591`
+...
+
+Here we see the highest error code in use is E0591
, so we probably want
+E0592
. To be sure, run rg E0592
and check, you should see no references.
You will have to write an extended description for your error,
+which will go in rustc_error_codes/src/error_codes/E0592.md
.
+To register the error, open rustc_error_codes/src/error_codes.rs
and add the
+code (in its proper numerical order) into register_diagnostics!
macro, like
+this:
+#![allow(unused)] +fn main() { +register_diagnostics! { + ... + E0592: include_str!("./error_codes/E0592.md"), +} +} +
To actually issue the error, you can use the struct_span_code_err!
macro:
+#![allow(unused)] +fn main() { +struct_span_code_err!(self.dcx(), // some path to the `DiagCtxt` here + span, // whatever span in the source you want + E0592, // your new error code + fluent::example::an_error_message) + .emit() // actually issue the error +} +
If you want to add notes or other snippets, you can invoke methods before you
+call .emit()
:
+#![allow(unused)] +fn main() { +struct_span_code_err!(...) + .span_label(another_span, fluent::example::example_label) + .span_note(another_span, fluent::example::separate_note) + .emit() +} +
For an example of a PR adding an error code, see #76143.
+ +ErrorGuaranteed
The previous sections have been about the error message that a user of the
+compiler sees. But emitting an error can also have a second important side
+effect within the compiler source code: it generates an
+ErrorGuaranteed
.
ErrorGuaranteed
is a zero-sized type that is unconstructable outside of the
+rustc_errors
crate. It is generated whenever an error is reported
+to the user, so that if your compiler code ever encounters a value of type
+ErrorGuaranteed
, the compilation is statically guaranteed to fail. This is
+useful for avoiding unsoundness bugs because you can statically check that an
+error code path leads to a failure.
There are some important considerations about the usage of ErrorGuaranteed
:
ErrorGuaranteed
when deciding whether to emit an error, or what kind of error
+to emit.ErrorGuaranteed
should not be used to indicate that a compilation will
+emit an error in the future. It should be used to indicate that an error
+has already been emitted -- that is, the emit()
function has
+already been called. For example, if we detect that a future part of the
+compiler will error, we cannot use ErrorGuaranteed
unless we first emit
+an error or delayed bug ourselves.Thankfully, in most cases, it should be statically impossible to abuse
+ErrorGuaranteed
.
This page documents some of the machinery around lint registration and how we +run lints in the compiler.
+The LintStore
is the central piece of infrastructure, around which
+everything rotates. The LintStore
is held as part of the Session
, and it
+gets populated with the list of lints shortly after the Session
is created.
There are two parts to the linting mechanism within the compiler: lints and +lint passes. Unfortunately, a lot of the documentation we have refers to both +of these as just "lints."
+First, we have the lint declarations themselves,
+and this is where the name and default lint level and other metadata come from.
+These are normally defined by way of the declare_lint!
macro,
+which boils down to a static with type &rustc_lint_defs::Lint
+(although this may change in the future,
+as the macro is somewhat unwieldy to add new fields to,
+like all macros).
As of Aug 2022, +we lint against direct declarations without the use of the macro.
+Lint declarations don't carry any "state" - they are merely global identifiers +and descriptions of lints. We assert at runtime that they are not registered +twice (by lint name).
+Lint passes are the meat of any lint. Notably, there is not a one-to-one +relationship between lints and lint passes; a lint might not have any lint pass +that emits it, it could have many, or just one -- the compiler doesn't track +whether a pass is in any way associated with a particular lint, and frequently +lints are emitted as part of other work (e.g., type checking, etc.).
+In rustc_interface::run_compiler
,
+the LintStore
is created,
+and all lints are registered.
There are three 'sources' of lints:
+rustc_interface::Config
register_lints
: lints passed into the compiler
+during constructionLints are registered via the LintStore::register_lint
function. This should
+happen just once for any lint, or an ICE will occur.
Once the registration is complete, we "freeze" the lint store by placing it in
+an Lrc
.
Lint passes are registered separately into one of the categories
+(pre-expansion, early, late, late module). Passes are registered as a closure
+-- i.e., impl Fn() -> Box<dyn X>
, where dyn X
is either an early or late
+lint pass trait object. When we run the lint passes, we run the closure and
+then invoke the lint pass methods. The lint pass methods take &mut self
so
+they can keep track of state internally.
These are lints used just by the compiler or drivers like clippy
. They can be
+found in rustc_lint::internal
.
An example of such a lint is the check that lint passes are implemented using
+the declare_lint_pass!
macro and not by hand. This is accomplished with the
+LINT_PASS_IMPL_WITHOUT_MACRO
lint.
Registration of these lints happens in the rustc_lint::register_internals
+function which is called when constructing a new lint store inside
+rustc_lint::new_lint_store
.
These are primarily described in two places,
+rustc_lint_defs::builtin
and rustc_lint::builtin
.
+Often the first provides the definitions for the lints themselves,
+and the latter provides the lint pass definitions (and implementations),
+but this is not always true.
The builtin lint registration happens in
+the rustc_lint::register_builtins
function.
+Just like with internal lints,
+this happens inside of rustc_lint::new_lint_store
.
These are the lints provided by drivers via the rustc_interface::Config
+register_lints
field, which is a callback. Drivers should, if finding it
+already set, call the function currently set within the callback they add. The
+best way for drivers to get access to this is by overriding the
+Callbacks::config
function which gives them direct access to the Config
+structure.
Within the compiler, for performance reasons, we usually do not register dozens
+of lint passes. Instead, we have a single lint pass of each variety (e.g.,
+BuiltinCombinedModuleLateLintPass
) which will internally call all of the
+individual lint passes; this is because then we get the benefits of static over
+dynamic dispatch for each of the (often empty) trait methods.
Ideally, we'd not have to do this, since it adds to the complexity of +understanding the code. However, with the current type-erased lint store +approach, it is beneficial to do so for performance reasons.
+ +Redirecting to... diagnostic-structs.html.
+ + diff --git a/diagnostics/translation.html b/diagnostics/translation.html new file mode 100644 index 000000000..f29414b83 --- /dev/null +++ b/diagnostics/translation.html @@ -0,0 +1,376 @@ + + + + + +Please see the tracking issue https://github.com/rust-lang/rust/issues/132181 +for status updates.
+We have downgraded the internal lints untranslatable_diagnostic
and
+diagnostic_outside_of_impl
. Those internal lints previously required new code
+to use the current translation infrastructure. However, because the translation
+infra is waiting for a yet-to-be-proposed redesign and thus rework, we are not
+mandating usage of current translation infra. Use the infra if you want to or
+otherwise makes the code cleaner, but otherwise sidestep the translation infra
+if you need more flexibility.
rustc's diagnostic infrastructure supports translatable diagnostics using +Fluent.
+There are two ways of writing translatable diagnostics:
+Diag
APIs (in
+Diagnostic
or Subdiagnostic
or LintDiagnostic
implementations).When adding or changing a translatable diagnostic,
+you don't need to worry about the translations.
+Only updating the original English message is required.
+Currently,
+each crate which defines translatable diagnostics has its own Fluent resource,
+which is a file named messages.ftl
,
+located in the root of the crate
+(such ascompiler/rustc_expand/messages.ftl
).
Fluent is built around the idea of "asymmetric localization", which aims to +decouple the expressiveness of translations from the grammar of the source +language (English in rustc's case). Prior to translation, rustc's diagnostics +relied heavily on interpolation to build the messages shown to the users. +Interpolated strings are hard to translate because writing a natural-sounding +translation might require more, less, or just different interpolation than the +English string, all of which would require changes to the compiler's source +code to support.
+Diagnostic messages are defined in Fluent resources. A combined set of Fluent
+resources for a given locale (e.g. en-US
) is known as Fluent bundle.
typeck_address_of_temporary_taken = cannot take address of a temporary
+
+In the above example, typeck_address_of_temporary_taken
is the identifier for
+a Fluent message and corresponds to the diagnostic message in English. Other
+Fluent resources can be written which would correspond to a message in another
+language. Each diagnostic therefore has at least one Fluent message.
typeck_address_of_temporary_taken = cannot take address of a temporary
+ .label = temporary value
+
+By convention, diagnostic messages for subdiagnostics are specified as
+"attributes" on Fluent messages (additional related messages, denoted by the
+.<attribute-name>
syntax). In the above example, label
is an attribute of
+typeck_address_of_temporary_taken
which corresponds to the message for the
+label added to this diagnostic.
Diagnostic messages often interpolate additional context into the message shown +to the user, such as the name of a type or of a variable. Additional context to +Fluent messages is provided as an "argument" to the diagnostic.
+typeck_struct_expr_non_exhaustive =
+ cannot create non-exhaustive {$what} using struct expression
+
+In the above example, the Fluent message refers to an argument named what
+which is expected to exist (how arguments are provided to diagnostics is
+discussed in detail later).
You can consult the Fluent documentation for other usage examples of Fluent +and its syntax.
+Usually, fluent uses -
for separating words inside a message name. However,
+_
is accepted by fluent as well. As _
fits Rust's use cases better, due to
+the identifiers on the Rust side using _
as well, inside rustc, -
is not
+allowed for separating words, and instead _
is recommended. The only exception
+is for leading -
s, for message names like -passes_see_issue
.
For a message to be translatable into different languages, all of the +information required by any language must be provided to the diagnostic as an +argument (not just the information required in the English message).
+As the compiler team gain more experience writing diagnostics that have all of +the information necessary to be translated into different languages, this page +will be updated with more guidance. For now, the Fluent documentation has +excellent examples of translating messages into different locales and the +information that needs to be provided by the code to do so.
+rustc's fluent_messages
macro performs compile-time validation of Fluent
+resources and generates code to make it easier to refer to Fluent messages in
+diagnostics.
Compile-time validation of Fluent resources will emit any parsing errors +from Fluent resources while building the compiler, preventing invalid Fluent +resources from causing panics in the compiler. Compile-time validation also +emits an error if multiple Fluent messages have the same identifier.
+Various parts of rustc's diagnostic internals are modified in order to support +translation.
+All of rustc's traditional diagnostic APIs (e.g. struct_span_err
or note
)
+take any message that can be converted into a DiagMessage
(or
+SubdiagMessage
).
rustc_error_messages::DiagMessage
can represent legacy non-translatable
+diagnostic messages and translatable messages. Non-translatable messages are
+just String
s. Translatable messages are just a &'static str
with the
+identifier of the Fluent message (sometimes with an additional &'static str
+with an attribute).
DiagMessage
never needs to be interacted with directly:
+DiagMessage
constants are created for each diagnostic message in a
+Fluent resource (described in more detail below), or DiagMessage
s will
+either be created in the macro-generated code of a diagnostic derive.
rustc_error_messages::SubdiagMessage
is similar, it can correspond to a
+legacy non-translatable diagnostic message or the name of an attribute to a
+Fluent message. Translatable SubdiagMessage
s must be combined with a
+DiagMessage
(using DiagMessage::with_subdiagnostic_message
) to
+be emitted (an attribute name on its own is meaningless without a corresponding
+message identifier, which is what DiagMessage
provides).
Both DiagMessage
and SubdiagMessage
implement Into
for any
+type that can be converted into a string, and converts these into
+non-translatable diagnostics - this keeps all existing diagnostic calls
+working.
Additional context for Fluent messages which are interpolated into message +contents needs to be provided to translatable diagnostics.
+Diagnostics have a set_arg
function that can be used to provide this
+additional context to a diagnostic.
Arguments have both a name (e.g. "what" in the earlier example) and a value.
+Argument values are represented using the DiagArgValue
type, which is
+just a string or a number. rustc types can implement IntoDiagArg
with
+conversion into a string or a number, and common types like Ty<'tcx>
already
+have such implementations.
set_arg
calls are handled transparently by diagnostic derives but need to be
+added manually when using diagnostic builder APIs.
rustc makes a distinction between the "fallback bundle" for en-US
that is used
+by default and when another locale is missing a message; and the primary fluent
+bundle which is requested by the user.
Diagnostic emitters implement the Emitter
trait which has two functions for
+accessing the fallback and primary fluent bundles (fallback_fluent_bundle
and
+fluent_bundle
respectively).
Emitter
also has member functions with default implementations for performing
+translation of a DiagMessage
using the results of
+fallback_fluent_bundle
and fluent_bundle
.
All of the emitters in rustc load the fallback Fluent bundle lazily, only
+reading Fluent resources and parsing them when an error message is first being
+translated (for performance reasons - it doesn't make sense to do this if no
+error is being emitted). rustc_error_messages::fallback_fluent_bundle
returns
+a std::lazy::Lazy<FluentBundle>
which is provided to emitters and evaluated
+in the first call to Emitter::fallback_fluent_bundle
.
The primary Fluent bundle (for the user's desired locale) is expected to be
+returned by Emitter::fluent_bundle
. This bundle is used preferentially when
+translating messages, the fallback bundle is only used if the primary bundle is
+missing a message or not provided.
There are no locale bundles distributed with the compiler, +but mechanisms are implemented for loading them.
+-Ztranslate-additional-ftl
can be used to load a specific resource as the
+primary bundle for testing purposes.-Ztranslate-lang
can be provided a language identifier (something like
+en-US
) and will load any Fluent resources found in
+$sysroot/share/locale/$locale/
directory (both the user provided
+sysroot and any sysroot candidates).Primary bundles are not currently loaded lazily and if requested will be loaded +at the start of compilation regardless of whether an error occurs. Lazily +loading primary bundles is possible if it can be assumed that loading a bundle +won't fail. Bundle loading can fail if a requested locale is missing, Fluent +files are malformed, or a message is duplicated in multiple resources.
+ +Understanding this page likely requires a rudimentary understanding of higher ranked
+trait bounds/for<'a>
and also what types such as dyn for<'a> Trait<'a>
and
+for<'a> fn(&'a u32)
mean. Reading the nomincon chapter
+on HRTB may be useful for understanding this syntax. The meaning of for<'a> fn(&'a u32)
+is incredibly similar to the meaning of T: for<'a> Trait<'a>
.
All function definitions conceptually have a ZST (this is represented by TyKind::FnDef
in rustc).
+The only generics on this ZST are the early bound parameters of the function definition. e.g.
+fn foo<'a>(_: &'a u32) {} + +fn main() { + let b = foo; + // ^ `b` has type `FnDef(foo, [])` (no args because `'a` is late bound) + assert!(std::mem::size_of_val(&b) == 0); +} +
In order to call b
the late bound parameters do need to be provided, these are inferred at the
+call site instead of when we refer to foo
.
+fn main() { + let b = foo; + let a: &'static u32 = &10; + foo(a); + // the lifetime argument for `'a` on `foo` is inferred at the callsite + // the generic parameter `'a` on `foo` is inferred to `'static` here +} +
Because late bound parameters are not part of the FnDef
's args this allows us to prove trait
+bounds such as F: for<'a> Fn(&'a u32)
where F
is foo
's FnDef
. e.g.
+fn foo_early<'a, T: Trait<'a>>(_: &'a u32, _: T) {} +fn foo_late<'a, T>(_: &'a u32, _: T) {} + +fn accepts_hr_func<F: for<'a> Fn(&'a u32, u32)>(_: F) {} + +fn main() { + // doesn't work, the substituted bound is `for<'a> FnDef<'?0>: Fn(&'a u32, u32)` + // `foo_early` only implements `for<'a> FnDef<'a>: Fn(&'a u32, u32)`- the lifetime + // of the borrow in the function argument must be the same as the lifetime + // on the `FnDef`. + accepts_hr_func(foo_early); + + // works, the substituted bound is `for<'a> FnDef: Fn(&'a u32, u32)` + accepts_hr_func(foo_late); +} + +// the builtin `Fn` impls for `foo_early` and `foo_late` look something like: +// `foo_early` +impl<'a, T: Trait<'a>> Fn(&'a u32, T) for FooEarlyFnDef<'a, T> { ... } +// `foo_late` +impl<'a, T> Fn(&'a u32, T) for FooLateFnDef<T> { ... } + +
Early bound parameters are present on the FnDef
. Late bound generic parameters are not present
+on the FnDef
but are instead constrained by the builtin Fn*
impl.
The same distinction applies to closures. Instead of FnDef
we are talking about the anonymous
+closure type. Closures are currently unsound in
+ways that are closely related to the distinction between early/late bound
+parameters (more on this later)
The early/late boundness of generic parameters is only relevant for the desugaring of
+functions/closures into types with builtin Fn*
impls. It does not make sense to talk about
+in other contexts.
The generics_of
query in rustc only contains early bound parameters. In this way it acts more
+like generics_of(my_func)
is the generics for the FnDef than the generics provided to the function
+body although it's not clear to the author of this section if this was the actual justification for
+making generics_of
behave this way.
Below are the current requirements for determining if a generic parameter is late bound. It is worth +keeping in mind that these are not necessarily set in stone and it is almost certainly possible to +be more flexible.
+Rust can't support types such as for<T> dyn Trait<T>
or for<T> fn(T)
, this is a
+fundamental limitation of the language as we are required to monomorphize type/const
+parameters and cannot do so behind dynamic dispatch. (technically we could probably
+support for<T> dyn MarkerTrait<T>
as there is nothing to monomorphize)
Not being able to support for<T> dyn Trait<T>
resulted in making all type and const
+parameters early bound. Only lifetime parameters can be late bound.
In order for a generic parameter to be late bound it must not appear in any where clauses. +This is currently an incredibly simplistic check that causes lifetimes to be early bound even +if the where clause they appear in are always true, or implied by well formedness of function +arguments. e.g.
++#![allow(unused)] +fn main() { +fn foo1<'a: 'a>(_: &'a u32) {} +// ^^ early bound parameter because it's in a `'a: 'a` clause +// even though the bound obviously holds all the time +fn foo2<'a, T: Trait<'a>(a: T, b: &'a u32) {} +// ^^ early bound parameter because it's used in the `T: Trait<'a>` clause +fn foo3<'a, T: 'a>(_: &'a T) {} +// ^^ early bound parameter because it's used in the `T: 'a` clause +// even though that bound is implied by wellformedness of `&'a T` +fn foo4<'a, 'b: 'a>(_: Inv<&'a ()>, _: Inv<&'b ()>) {} +// ^^ ^^ ^^^ note: +// ^^ ^^ `Inv` stands for `Invariant` and is used to +// ^^ ^^ make the type parameter invariant. This +// ^^ ^^ is necessary for demonstration purposes as +// ^^ ^^ `for<'a, 'b> fn(&'a (), &'b ())` and +// ^^ ^^ `for<'a> fn(&'a u32, &'a u32)` are subtypes- +// ^^ ^^ of each other which makes the bound trivially +// ^^ ^^ satisfiable when making the fnptr. `Inv` +// ^^ ^^ disables this subtyping. +// ^^ ^^ +// ^^^^^^ both early bound parameters because they are present in the +// `'b: 'a` clause +} +
The reason for this requirement is that we cannot represent the T: Trait<'a>
or 'a: 'b
clauses
+on a function pointer. for<'a, 'b> fn(Inv<&'a ()>, Inv<&'b ()>)
is not a valid function pointer to
+representfoo4
as it would allow calling the function without 'b: 'a
holding.
The builtin impls of the Fn*
traits for closures and FnDef
s cannot not have any unconstrained
+parameters. For example the following impl is illegal:
+#![allow(unused)] +fn main() { +impl<'a> Trait for u32 { type Assoc = &'a u32; } +} +
We must not end up with a similar impl for the Fn*
traits e.g.
+#![allow(unused)] +fn main() { +impl<'a> Fn<()> for FnDef { type Assoc = &'a u32 } +} +
Violating this rule can trivially lead to unsoundness as seen in #84366. +Additionally if we ever support late bound type params then an impl like:
++#![allow(unused)] +fn main() { +impl<T> Fn<()> for FnDef { type Assoc = T; } +} +
would break the compiler in various ways.
+In order to ensure that everything functions correctly, we do not allow generic parameters to +be late bound if it would result in a builtin impl that does not constrain all of the generic +parameters on the builtin impl. Making a generic parameter be early bound trivially makes it be +constrained by the builtin impl as it ends up on the self type.
+Because of the requirement that late bound parameters must not appear in where clauses, checking +this is simpler than the rules for checking impl headers constrain all the parameters on the impl. +We only have to ensure that all late bound parameters appear at least once in the function argument +types outside of an alias (e.g. an associated type).
+The requirement that they not indirectly be in the args of an alias for it to count is the +same as why the follow code is forbidden:
++#![allow(unused)] +fn main() { +impl<T: Trait> OtherTrait for <T as Trait>::Assoc { type Assoc = T } +} +
There is no guarantee that <T as Trait>::Assoc
will normalize to different types for every
+instantiation of T
. If we were to allow this impl we could get overlapping impls and the
+same is true of the builtin Fn*
impls.
It is generally considered desirable for more parameters to be late bound as it makes
+the builtin Fn*
impls more flexible. Right now many of the requirements for making
+a parameter late bound are overly restrictive as they are tied to what we can currently
+(or can ever) do with fn ptrs.
It would be theoretically possible to support late bound params in where
-clauses in the
+language by introducing implication types which would allow us to express types such as:
+for<'a, 'b: 'a> fn(Inv<&'a u32>, Inv<&'b u32>)
which would ensure 'b: 'a
is upheld when
+calling the function pointer.
It would also be theoretically possible to support it by making the coercion to a fn ptr
+instantiate the parameter with an infer var while still allowing the FnDef to not have the
+generic parameter present as trait impls are perfectly capable of representing the where clauses
+on the function on the impl itself. This would also allow us to support late bound type/const
+vars allowing bounds like F: for<T> Fn(T)
to hold.
It is almost somewhat unclear if we can change the Fn
traits to be structured differently
+so that we never have to make a parameter early bound just to make the builtin impl have all
+generics be constrained. Of all the possible causes of a generic parameter being early bound
+this seems the most difficult to remove.
Whether these would be good ideas to implement is a separate question- they are only brought +up to illustrate that the current rules are not necessarily set in stone and a result of +"its the only way of doing this".
+ +This section discusses what it means for generic parameters to be early or late bound.
++#![allow(unused)] +fn main() { +fn foo<'a, T>(b: &'a T) -> &'a T { b } +// ^^ ^early bound +// ^^ +// ^^late bound +} +
Generally when referring to an item with generic parameters you must specify a list of generic arguments corresponding to the item's generic parameters. In
+some cases it is permitted to elide these arguments but still, implicitly, a set of arguments are provided (i.e. Vec::default()
desugars to Vec::<_>::default()
).
For functions this is not necessarily the case, for example if we take the function foo
from the example above and write the following code:
+fn main() { + let f = foo::<_>; + + let b = String::new(); + let c = String::new(); + + f(&b); + drop(b); + f(&c); +} +
This code compiles perfectly fine even though there is no single lifetime that could possibly be specified in foo::<_>
that would allow for both
+the &b
and &c
borrows to be used as arguments (note: the drop(b)
line forces the &b
borrow to be shorter than the &c
borrow). This works because
+the 'a
lifetime is late bound.
A generic parameter being late bound means that when we write foo::<_>
we do not actually provide an argument for that parameter, instead we wait until calling the function to provide the generic argument. In the above example this means that we are doing something like f::<'_>(&b);
and f::<'_>(&c);
(although in practice we do not actually support turbofishing late bound parameters in this manner)
It may be helpful to think of "early bound parameter" or "late bound parameter" as meaning "early provided parameter" and "late provided parameter", i.e. we provide the argument to the parameter either early (when naming the function) or late (when calling it).
+ +The early/late bound parameter distinction on functions introduces some complications +when providing generic arguments to functions. This document discusses what those are +and how they might interact with future changes to make more things late bound.
+When a function has any late bound lifetime parameters (be they explicitly defined or
+implicitly introduced via lifetime elision) we disallow specifying any lifetime arguments
+on the function. Sometimes this is a hard error other times it is a future compat lint
+(late_bound_lifetime_arguments
).
+fn early<'a: 'a>(a: &'a ()) -> &'a () { a } +fn late<'a>(a: &'a ()) -> &'a () { a } + +fn mixed<'a, 'b: 'b>(a: &'a (), b: &'b ()) -> &'a () { a } + +struct Foo; +impl Foo { + fn late<'a>(self, a: &'a ()) -> &'a () { a } +} + +fn main() { + // fine + let f = early::<'static>; + + // some variation of hard errors and future compat lints + Foo.late::<'static>(&()); + let f = late::<'static>; + let f = mixed::<'static, 'static>; + let f = mixed::<'static>; + late::<'static>(&()); +} +
The justification for this is that late bound parameters are not present on the
+FnDef
so the arguments to late bound parameters can't be present in the substs
+for the type. i.e. the late
function in the above code snippet would not have
+any generic parameters on the FnDef
zst:
+#![allow(unused)] +fn main() { +// example desugaring of the `late` function and its zst + builtin Fn impl +struct LateFnDef; +impl<'a> Fn<(&'a ())> for LateFnDef { + type Output = &'a (); + ... +} +} +
The cause for some situations giving future compat lints and others giving hard errors +is a little arbitrary but explainable:
+Because of the previously mentioned restriction on turbofishing generic arguments, it +is a breaking change to upgrade a lifetime from early bound to late bound as it can cause +existing turbofishies to become hard errors/future compat lints.
+Many t-types members have expressed interest in wanting more parameters to be late bound. +We cannot do so if making something late bound is going to break code that many would +expect to work (judging by the future compat lint issue many people do expect to be able +to turbofish late bound parameters).
+If we were to make some type/const parameters late bound we would definitely not want +to disallow turbofishing them as it presumably(?) would break a Tonne of code.
+While lifetimes do differ from type/consts in some ways I(BoxyUwU) do not believe there +is any justification for why it would make sense to allow turbofishing late bound +type/const parameters but not late bound lifetimes.
+From reasons above it seems reasonable that we may want to remove the hard error and fcw +(removing the errors/fcw is definitely a blocker for making more things late bound).
+example behaviour:
++fn late<'a>(a: &'a ()) -> &'a () { a } + +fn accepts_fn(_: impl for<'a> Fn(&'a ()) -> &'a ()) {} +fn accepts_fn_2(_: impl Fn(&'static ()) -> &'static ()) {} + +fn main() { + let f = late::<'static>; + + accepts_fn(f); //~ error: `f` doesn't implement `for<'a> Fn(&'a ()) -> &'a ()` + accepts_fn_2(f) // works + + accepts_fn(late) // works +} +
one potential complication is that we would want a way to specify a generic argument +to a function without having to specify arguments for all previous parameters. i.e. +ideally you could write the following code somehow.
++fn late<'a, 'b>(_: &'a (), _: &'b ()) {} + +fn accepts_fn(_: impl for<'a> Fn(&'a (), &'static ())) {} + +fn main() { + // a naive implementation would have a `ReInfer` as the subst for `'a` parameter + // no longer allowing the FnDef to satisfy the `for<'a> Fn(&'a ()` bound + let f = late::<'_, 'static>; + accepts_fn(f); +} +
Maybe we can just special case HIR ty lowering for _
/'_
arguments for late bound
+parameters somehow and have it not mean the same thing as _
for early bound parameters.
+Regardless I think we would need a solution that would allow writing the above code even
+if it was done by some new syntax such as having to write late::<k#no_argument, 'static>
+(naturally k#no_argument
would only make sense as an argument to late bound parameters).
Note: all of this describes the implementation of the unstable effects
and
+const_trait_impl
features. None of this implementation is usable or visible from
+stable Rust.
The implementation of const traits and ~const
bounds is a limited effect system.
+It is used to allow trait bounds on const fn
to be used within the const fn
for
+method calls. Within the function, in order to know whether a method on a trait
+bound is const
, we need to know whether there is a ~const
bound for the trait.
+In order to know whether we can instantiate a ~const
bound on a const fn
, we
+need to know whether there is a const_trait
impl for the type and trait being
+used (or whether the const fn
is used at runtime, then any type implementing the
+trait is ok, just like with other bounds).
We perform these checks via a const generic boolean that gets attached to all
+const fn
and const trait
. The following sections will explain the desugarings
+and the way we perform the checks at call sites.
The const generic boolean is inverted to the meaning of const
. In the compiler
+it is called host
, because it enables "host APIs" like static
items, network
+access, disk access, random numbers and everything else that isn't available in
+const
contexts. So false
means "const", true
means "not const" and if it's
+a generic parameter, it means "maybe const" (meaning we're in a const fn or const
+trait).
const fn
All const fn
have a #[rustc_host] const host: bool
generic parameter that is
+hidden from users. Any ~const Trait
bounds in the generics list or where
bounds
+of a const fn
get converted to Trait<host> + Trait<true>
bounds. The Trait<true>
+exists so that associated types of the generic param can be used from projections
+like <T as Trait>::Assoc
, because there are no <T as ~const Trait>
projections for now.
#[const_trait] trait
sThe #[const_trait]
attribute gives the marked trait a #[rustc_host] const host: bool
+generic parameter. All functions of the trait "inherit" this generic parameter, just like
+they have all the regular generic parameters of the trait. Any ~const Trait
super-trait
+bounds get desugared to Trait<host> + Trait<true>
in order to allow using associated
+types and consts of the super traits in the trait declaration. This is necessary, because
+<Self as SuperTrait>::Assoc
is always <Self as SuperTrait<true>>::Assoc
as there is
+no <Self as ~const SuperTrait>
syntax.
typeck
performing method and function call checks.When generic parameters are instantiated for any items, the host
generic parameter
+is always instantiated as an inference variable. This is a special kind of inference var
+that is not part of the type or const inference variables, similar to how we have
+special inference variables for type variables that we know to be an integer, but not
+yet which one. These separate inference variables fall back to true
at
+the end of typeck (in fallback_effects
) to ensure that let _ = some_fn_item_name;
+will keep compiling.
All actually used (in function calls, casts, or anywhere else) function items, will
+have the enforce_context_effects
method invoked.
+It trivially returns if the function being called has no host
generic parameter.
In order to error if a non-const function is called in a const context, we have not
+yet disabled the const-check logic that happens on MIR, because
+enforce_context_effects
does not yet perform this check.
The function call's host
parameter is then equated to the context's host
value,
+which almost always trivially succeeds, as it was an inference var. If the inference
+var has already been bound (since the function item is invoked twice), the second
+invocation checks it against the first.
The rust-lang/rust
git repository depends on several other repos in the rust-lang
organization.
+There are three main ways we use dependencies:
rustc-rayon
)clippy
)cargo
)As a general rule, use crates.io for libraries that could be useful for others in the ecosystem; use +subtrees for tools that depend on compiler internals and need to be updated if there are breaking +changes; and use submodules for tools that are independent of the compiler.
+As a developer to this repository, you don't have to treat the following external projects +differently from other crates that are directly in this repo:
+In contrast to submodule
dependencies
+(see below for those), the subtree
dependencies are just regular files and directories which can
+be updated in tree. However, if possible, enhancements, bug fixes, etc. specific
+to these tools should be filed against the tools directly in their respective
+upstream repositories. The exception is that when rustc changes are required to
+implement a new tool feature or test, that should happen in one collective rustc PR.
Periodically the changes made to subtree based dependencies need to be synchronized between this +repository and the upstream tool repositories.
+Subtree synchronizations are typically handled by the respective tool maintainers. Other users +are welcome to submit synchronization PRs, however, in order to do so you will need to modify +your local git installation and follow a very precise set of instructions. +These instructions are documented, along with several useful tips and tricks, in the +syncing subtree changes section in Clippy's Contributing guide. +The instructions are applicable for use with any subtree based tool, just be sure to +use the correct corresponding subtree directory and remote repository.
+The synchronization process goes in two directions: subtree push
and subtree pull
.
A subtree push
takes all the changes that happened to the copy in this repo and creates commits
+on the remote repo that match the local changes. Every local
+commit that touched the subtree causes a commit on the remote repo, but
+is modified to move the files from the specified directory to the tool repo root.
A subtree pull
takes all changes since the last subtree pull
+from the tool repo and adds these commits to the rustc repo along with a merge commit that moves
+the tool changes into the specified directory in the Rust repository.
It is recommended that you always do a push first and get that merged to the tool master branch.
+Then, when you do a pull, the merge works without conflicts.
+While it's definitely possible to resolve conflicts during a pull, you may have to redo the conflict
+resolution if your PR doesn't get merged fast enough and there are new conflicts. Do not try to
+rebase the result of a git subtree pull
, rebasing merge commits is a bad idea in general.
You always need to specify the -P
prefix to the subtree directory and the corresponding remote
+repository. If you specify the wrong directory or repository
+you'll get very fun merges that try to push the wrong directory to the wrong remote repository.
+Luckily you can just abort this without any consequences by throwing away either the pulled commits
+in rustc or the pushed branch on the remote and try again. It is usually fairly obvious
+that this is happening because you suddenly get thousands of commits that want to be synchronized.
If you want to create a new subtree dependency from an existing repository, call (from this +repository's root directory!)
+git subtree add -P src/tools/clippy https://github.com/rust-lang/rust-clippy.git master
+
+This will create a new commit, which you may not rebase under any circumstances! Delete the commit +and redo the operation if you need to rebase.
+Now you're done, the src/tools/clippy
directory behaves as if Clippy were
+part of the rustc monorepo, so no one but you (or others that synchronize
+subtrees) actually needs to use git subtree
.
Building Rust will also use external git repositories tracked using git
+submodules. The complete list may be found in the .gitmodules
file. Some
+of these projects are required (like stdarch
for the standard library) and
+some of them are optional (like src/doc/book
).
Usage of submodules is discussed more in the Using Git chapter.
+Some of the submodules are allowed to be in a "broken" state where they +either don't build or their tests don't pass, e.g. the documentation books +like The Rust Reference. Maintainers of these projects will be notified +when the project is in a broken state, and they should fix them as soon +as possible. The current status is tracked on the toolstate website. +More information may be found on the Forge Toolstate chapter. +In practice, it is very rare for documentation to have broken toolstate.
+Breakage is not allowed in the beta and stable channels, and must be addressed +before the PR is merged. They are also not allowed to be broken on master in +the week leading up to the beta cut.
+ +TODO: this chapter #1158
+ +This chapter is intended to provide basic help for adding, removing, and +modifying feature gates.
+Note that this is specific to language feature gates; library feature gates use a different +mechanism.
+See "Stability in code" in the "Implementing new features" section for instructions.
+To remove a feature gate, follow these steps:
+Remove the feature gate declaration in rustc_feature/src/unstable.rs
.
+It will look like this:
/// description of feature
+(unstable, $feature_name, "$version", Some($tracking_issue_number))
+
+Add a modified version of the feature gate declaration that you just
+removed to rustc_feature/src/removed.rs
:
/// description of feature
+(removed, $old_feature_name, "$version", Some($tracking_issue_number),
+ Some("$why_it_was_removed"))
+
+To rename a feature gate, follow these steps (the first two are the same steps +to follow when removing a feature gate):
+Remove the old feature gate declaration in rustc_feature/src/unstable.rs
.
+It will look like this:
/// description of feature
+(unstable, $old_feature_name, "$version", Some($tracking_issue_number))
+
+Add a modified version of the old feature gate declaration that you just
+removed to rustc_feature/src/removed.rs
:
/// description of feature
+/// Renamed to `$new_feature_name`
+(removed, $old_feature_name, "$version", Some($tracking_issue_number),
+ Some("renamed to `$new_feature_name`"))
+
+Add a feature gate declaration with the new name to
+rustc_feature/src/unstable.rs
. It should look very similar to the old
+declaration:
/// description of feature
+(unstable, $new_feature_name, "$version", Some($tracking_issue_number))
+
+See "Updating the feature-gate listing" in the "Stabilizing Features" chapter +for instructions. There are additional steps you will need to take beyond just +updating the declaration!
+ +For the purposes of this guide, fuzzing is any testing methodology that +involves compiling a wide variety of programs in an attempt to uncover bugs in +rustc. Fuzzing is often used to find internal compiler errors (ICEs). Fuzzing +can be beneficial, because it can find bugs before users run into them and +provide small, self-contained programs that make the bug easier to track down. +However, some common mistakes can reduce the helpfulness of fuzzing and end up +making contributors' lives harder. To maximize your positive impact on the Rust +project, please read this guide before reporting fuzzer-generated bugs!
+Please do:
+rustfmt
, if it maintains the bugPlease don't:
+custom_mir
, lang_items
, no_core
, and rustc_attrs
.If you're not sure whether or not an ICE is a duplicate of one that's already +been reported, please go ahead and report it and link to issues you think might +be related. In general, ICEs on the same line but with different query stacks +are usually distinct bugs. For example, #109020 and #109129 +had similar error messages:
+error: internal compiler error: compiler/rustc_middle/src/ty/normalize_erasing_regions.rs:195:90: Failed to normalize <[closure@src/main.rs:36:25: 36:28] as std::ops::FnOnce<(Emplacable<()>,)>>::Output, maybe try to call `try_normalize_erasing_regions` instead
+
+error: internal compiler error: compiler/rustc_middle/src/ty/normalize_erasing_regions.rs:195:90: Failed to normalize <() as Project>::Assoc, maybe try to call `try_normalize_erasing_regions` instead
+
+but different query stacks:
+query stack during panic:
+#0 [fn_abi_of_instance] computing call ABI of `<[closure@src/main.rs:36:25: 36:28] as core::ops::function::FnOnce<(Emplacable<()>,)>>::call_once - shim(vtable)`
+end of query stack
+
+query stack during panic:
+#0 [check_mod_attrs] checking attributes in top-level module
+#1 [analysis] running analysis passes on this crate
+end of query stack
+
+When building a corpus, be sure to avoid collecting tests that are already +known to crash rustc. A fuzzer that is seeded with such tests is more likely to +generate bugs with the same root cause, wasting everyone's time. The simplest +way to avoid this is to loop over each file in the corpus, see if it causes an +ICE, and remove it if so.
+To build a corpus, you may want to use:
+// failure-status: 101
or // known-bug: #NNN
.ices/
!Here are a few things you can do to help the Rust project after filing an ICE.
+It is helpful to carefully minimize the fuzzer-generated input. When +minimizing, be careful to preserve the original error, and avoid introducing +distracting problems such as syntax, type-checking, or borrow-checking errors.
+There are some tools that can help with minimization. If you're not sure how
+to avoid introducing syntax, type-, and borrow-checking errors while using
+these tools, post both the complete and minimized test cases. Generally,
+syntax-aware tools give the best results in the least amount of time.
+treereduce-rust
and picireny are syntax-aware.
+halfempty
is not, but is generally a high-quality tool.
When fuzzing rustc, you may want to avoid generating machine code, since this
+is mostly done by LLVM. Try --emit=mir
instead.
A variety of compiler flags can uncover different issues. -Zmir-opt-level=4
+will turn on MIR optimization passes that are not run by default, potentially
+uncovering interesting bugs. -Zvalidate-mir
can help uncover such bugs.
If you're fuzzing a compiler you built, you may want to build it with -C target-cpu=native
or even PGO/BOLT to squeeze out a few more executions per
+second. Of course, it's best to try multiple build configurations and see
+what actually results in superior throughput.
You may want to build rustc from source with debug assertions to find
+additional bugs, though this is a trade-off: it can slow down fuzzing by
+requiring extra work for every execution. To enable debug assertions, add this
+to config.toml
when compiling rustc:
[rust]
+debug-assertions = true
+
+ICEs that require debug assertions to reproduce should be tagged
+requires-debug-assertions
.
This chapter will discuss how rustc tracks what generic parameters are introduced by an item. For example given some struct defined via struct Foo<T>
how does rustc track that Foo
defines some type parameter T
and nothing else?
This will not cover how we track generic parameters introduced via for<'a>
syntax (i.e. in where clauses or fn
types), which is covered elsewhere in the chapter on Binder
s .
Thank you for your interest in contributing to Rust! There are many ways to +contribute, and we appreciate all of them.
+If this is your first time contributing, the walkthrough chapter can give you a good example of +how a typical contribution would go.
+This documentation is not intended to be comprehensive; it is meant to be a +quick guide for the most useful things. For more information, see this +chapter on how to build and run the compiler.
+If you have questions, please make a post on the Rust Zulip server or
+internals.rust-lang.org. If you are contributing to Rustup, be aware they are not on
+Zulip - you can ask questions in #wg-rustup
on Discord.
+See the list of teams and working groups and the Community page on the
+official website for more resources.
As a reminder, all contributors are expected to follow our Code of Conduct.
+The compiler team (or t-compiler
) usually hangs out in Zulip in this
+"stream"; it will be easiest to get questions answered there.
Please ask questions! A lot of people report feeling that they are "wasting
+expert time", but nobody on t-compiler
feels this way. Contributors are
+important to us.
Also, if you feel comfortable, prefer public topics, as this means others can +see the questions and answers, and perhaps even integrate them back into this +guide :)
+Not all t-compiler
members are experts on all parts of rustc
; it's a
+pretty large project. To find out who could have some expertise on
+different parts of the compiler, consult triagebot assign groups.
+The sections that start with [assign*
in triagebot.toml
file.
+But also, feel free to ask questions even if you can't figure out who to ping.
Another way to find experts for a given part of the compiler is to see who has made recent commits.
+For example, to find people who have recently worked on name resolution since the 1.68.2 release,
+you could run git shortlog -n 1.68.2.. compiler/rustc_resolve/
. Ignore any commits starting with
+"Rollup merge" or commits by @bors
(see CI contribution procedures for
+more information about these commits).
We do ask that you be mindful to include as much useful information as you can +in your question, but we recognize this can be hard if you are unfamiliar with +contributing to Rust.
+Just pinging someone without providing any context can be a bit annoying and
+just create noise, so we ask that you be mindful of the fact that the
+t-compiler
folks get a lot of pings in a day.
The Rust project is quite large and it can be difficult to know which parts of the project need +help, or are a good starting place for beginners. Here are some suggested starting places.
+If you're looking for somewhere to start, check out the following issue +search. See the Triage for an explanation of these labels. You can also try +filtering the search to areas you're interested in. For example:
+repo:rust-lang/rust-clippy
will only show clippy issueslabel:T-compiler
will only show issues related to the compilerlabel:A-diagnostics
will only show diagnostic issuesNot all important or beginner work has issue labels. +See below for how to find work that isn't labelled.
+Some work is too large to be done by a single person. In this case, it's common to have "Tracking +issues" to co-ordinate the work between contributors. Here are some example tracking issues where +it's easy to pick up work without a large time commitment:
+If you find more recurring work, please feel free to add it here!
+The Clippy project has spent a long time making its contribution process as friendly to newcomers +as possible. Consider working on it first to get familiar with the process and the compiler +internals.
+See the Clippy contribution guide for instructions on getting started.
+Many diagnostic issues are self-contained and don't need detailed background knowledge of the +compiler. You can see a list of diagnostic issues here.
+Sometimes, contributors send a pull request, but later find out that they don't have enough
+time to work on it, or they simply are not interested in it anymore. Such PRs are often
+eventually closed and they receive the S-inactive
label. You could try to examine some of
+these PRs and pick up the work. You can find the list of such PRs here.
If the PR has been implemented in some other way in the meantime, the S-inactive
label
+should be removed from it. If not, and it seems that there is still interest in the change,
+you can try to rebase the pull request on top of the latest master
branch and send a new
+pull request, continuing the work on the feature.
See std-dev-guide.
+There are a bunch of other projects that you can contribute to outside of the
+rust-lang/rust
repo, including cargo
, miri
, rustup
, and many others.
These repos might have their own contributing guidelines and procedures. Many +of them are owned by working groups. For more info, see the documentation in those repos' READMEs.
+There are a bunch of other ways you can contribute, especially if you don't
+feel comfortable jumping straight into the large rust-lang/rust
codebase.
The following tasks are doable without much background knowledge but are +incredibly helpful:
+See "How to build and run the compiler".
+This section has moved to the "Contribution Procedures" chapter.
+This section has moved to the "About this guide" chapter.
+ +<<< HEAD
?git blame
The Rust project uses Git to manage its source code. In order to +contribute, you'll need some familiarity with its features so that your changes +can be incorporated into the compiler.
+The goal of this page is to cover some of the more common questions and +problems new contributors face. Although some Git basics will be covered here, +if you find that this is still a little too fast for you, it might make sense +to first read some introductions to Git, such as the Beginner and Getting +started sections of this tutorial from Atlassian. GitHub also +provides documentation and guides for beginners, or you can consult the +more in depth book from Git.
+This guide is incomplete. If you run into trouble with git that this page doesn't help with, +please open an issue so we can document how to fix it.
+We'll assume that you've installed Git, forked rust-lang/rust, and cloned the +forked repo to your PC. We'll use the command line interface to interact +with Git; there are also a number of GUIs and IDE integrations that can +generally do the same things.
+If you've cloned your fork, then you will be able to reference it with origin
+in your local repo. It may be helpful to also set up a remote for the official
+rust-lang/rust repo via
git remote add upstream https://github.com/rust-lang/rust.git
+
+if you're using HTTPS, or
+git remote add upstream git@github.com:rust-lang/rust.git
+
+if you're using SSH.
+NOTE: This page is dedicated to workflows for rust-lang/rust
, but will likely be
+useful when contributing to other repositories in the Rust project.
Below is the normal procedure that you're likely to use for most minor changes +and PRs:
+git checkout master
.git pull upstream master --ff-only
.
+(see No-Merge Policy for more info about this).git checkout -b issue-12345-fix
.git add src/changed/file.rs src/another/change.rs
+and then commit them with git commit
. Of course, making intermediate commits
+may be a good idea as well. Avoid git add .
, as it makes it too easy to
+unintentionally commit changes that should not be committed, such as submodule
+updates. You can use git status
to check if there are any files you forgot
+to stage.git push --set-upstream origin issue-12345-fix
+(After adding commits, you can use git push
and after rebasing or
+pulling-and-rebasing, you can use git push --force-with-lease
).rust-lang/rust
's master branch.If you end up needing to rebase and are hitting conflicts, see Rebasing. +If you want to track upstream while working on long-running feature/issue, see +Keeping things up to date.
+If your reviewer requests changes, the procedure for those changes looks much +the same, with some steps skipped:
+git checkout issue-12345-fix
.git push
.You don't need to clone rust-lang/rust
from scratch if it's out of date!
+Even if you think you've messed it up beyond repair, there are ways to fix
+the git state that don't require downloading the whole repository again.
+Here are some common issues you might run into:
Git has two ways to update your branch with the newest changes: merging and rebasing.
+Rust uses rebasing. If you make a merge commit, it's not too hard to fix:
+git rebase -i upstream/master
.
See Rebasing for more about rebasing.
+This is not a problem from git's perspective. If you run git remote -v
,
+it will say something like this:
$ git remote -v
+origin git@github.com:jyn514/rust.git (fetch)
+origin git@github.com:jyn514/rust.git (push)
+upstream https://github.com/rust-lang/rust (fetch)
+upstream https://github.com/rust-lang/rust (fetch)
+
+If you renamed your fork, you can change the URL like this:
+git remote set-url origin <URL>
+
+where the <URL>
is your new fork.
Usually people notice this when rustbot posts a comment on github that cargo
has been modified:
You might also notice conflicts in the web UI:
+ +The most common cause is that you rebased after a change and ran git add .
without first running
+x
to update the submodules. Alternatively, you might have run cargo fmt
instead of x fmt
+and modified files in a submodule, then committed the changes.
To fix it, do the following things:
+git log --stat -n1 src/tools/cargo
git checkout <my-commit>~ src/tools/cargo
. Type ~
+literally but replace <my-commit>
with the output from step 1.git commit --fixup <my-commit>
git log
command shows a commit
+that's not authored by you.git rebase --autosquash -i upstream/master
These are two common errors to see when rebasing:
+error: cannot rebase: Your index contains uncommitted changes.
+error: Please commit or stash them.
+
+error: cannot rebase: You have unstaged changes.
+error: Please commit or stash them.
+
+(See https://git-scm.com/book/en/v2/Getting-Started-What-is-Git%3F#_the_three_states for the difference between the two.)
+This means you have made changes since the last time you made a commit. To be able to rebase, either +commit your changes, or make a temporary commit called a "stash" to have them still not be committed +when you finish rebasing. You may want to configure git to make this "stash" automatically, which +will prevent the "cannot rebase" error in nearly all cases:
+git config --global rebase.autostash true
+
+See https://git-scm.com/book/en/v2/Git-Tools-Stashing-and-Cleaning for more info about stashing.
+This is left over from the move to the library/
directory.
+Unfortunately, git rebase
does not follow renames for submodules, so you
+have to delete the directory yourself:
rm -r src/stdarch
+
+<<< HEAD
?You were probably in the middle of a rebase or merge conflict. See
+Conflicts for how to fix the conflict. If you don't care about the changes
+and just want to get a clean copy of the repository back, you can use git reset
:
# WARNING: this throws out any local changes you've made! Consider resolving the conflicts instead.
+git reset --hard master
+
+git push
will not work properly and say something like this:
! [rejected] issue-xxxxx -> issue-xxxxx (non-fast-forward)
+error: failed to push some refs to 'https://github.com/username/rust.git'
+hint: Updates were rejected because the tip of your current branch is behind
+hint: its remote counterpart. Integrate the remote changes (e.g.
+hint: 'git pull ...') before pushing again.
+hint: See the 'Note about fast-forwards' in 'git push --help' for details.
+
+The advice this gives is incorrect! Because of Rust's
+"no-merge" policy the merge commit created by git pull
+will not be allowed in the final PR, in addition to defeating the point of the
+rebase! Use git push --force-with-lease
instead.
If you see many commits in your rebase list, or merge commits, or commits by other people that you
+didn't write, it likely means you're trying to rebase over the wrong branch. For example, you may
+have a rust-lang/rust
remote upstream
, but ran git rebase origin/master
instead of git rebase upstream/master
. The fix is to abort the rebase and use the correct branch instead:
git rebase --abort
+git rebase -i upstream/master
+
+When updating your local repository with git pull
, you may notice that sometimes
+Git says you have modified some files that you have never edited. For example,
+running git status
gives you something like (note the new commits
mention):
On branch master
+Your branch is up to date with 'origin/master'.
+
+Changes not staged for commit:
+ (use "git add <file>..." to update what will be committed)
+ (use "git restore <file>..." to discard changes in working directory)
+ modified: src/llvm-project (new commits)
+ modified: src/tools/cargo (new commits)
+
+no changes added to commit (use "git add" and/or "git commit -a")
+
+These changes are not changes to files: they are changes to submodules (more on this
+later). To get rid of those, run ./x --help
, which will automatically update
+the submodules.
Some submodules are not actually needed; for example, src/llvm-project
doesn't need to be checked
+out if you're using download-ci-llvm
. To avoid having to keep fetching its history, you can use
+git submodule deinit -f src/llvm-project
, which will also avoid it showing as modified again.
When you edit your code locally, you are making changes to the version of +rust-lang/rust that existed when you created your feature branch. As such, when +you submit your PR it is possible that some of the changes that have been made +to rust-lang/rust since then are in conflict with the changes you've made. +When this happens, you need to resolve the conflicts before your changes can be +merged. To do that, you need to rebase your work on top of rust-lang/rust.
+To rebase your feature branch on top of the newest version of the master branch +of rust-lang/rust, checkout your branch, and then run this command:
+git pull --rebase https://github.com/rust-lang/rust.git master
+
+++If you are met with the following error:
++error: cannot pull with rebase: Your index contains uncommitted changes. +error: please commit or stash them. +
it means that you have some uncommitted work in your working tree. In that +case, run
+git stash
before rebasing, and thengit stash pop
after you +have rebased and fixed all conflicts.
When you rebase a branch on master, all the changes on your branch are +reapplied to the most recent version of master. In other words, Git tries to +pretend that the changes you made to the old version of master were instead +made to the new version of master. During this process, you should expect to +encounter at least one "rebase conflict." This happens when Git's attempt to +reapply the changes fails because your changes conflicted with other changes +that have been made. You can tell that this happened because you'll see +lines in the output that look like
+CONFLICT (content): Merge conflict in file.rs
+
+When you open these files, you'll see sections of the form
+<<<<<<< HEAD
+Original code
+=======
+Your code
+>>>>>>> 8fbf656... Commit fixes 12345
+
+This represents the lines in the file that Git could not figure out how to
+rebase. The section between <<<<<<< HEAD
and =======
has the code from
+master, while the other side has your version of the code. You'll need to
+decide how to deal with the conflict. You may want to keep your changes,
+keep the changes on master, or combine the two.
Generally, resolving the conflict consists of two steps: First, fix the
+particular conflict. Edit the file to make the changes you want and remove the
+<<<<<<<
, =======
and >>>>>>>
lines in the process. Second, check the
+surrounding code. If there was a conflict, its likely there are some logical
+errors lying around too! It's a good idea to run x check
here to make sure
+there are no glaring errors.
Once you're all done fixing the conflicts, you need to stage the files that had
+conflicts in them via git add
. Afterwards, run git rebase --continue
to let
+Git know that you've resolved the conflicts and it should finish the rebase.
Once the rebase has succeeded, you'll want to update the associated branch on
+your fork with git push --force-with-lease
.
The above section on Rebasing is a specific +guide on rebasing work and dealing with merge conflicts. +Here is some general advice about how to keep your local repo +up-to-date with upstream changes:
+Using git pull upstream master
while on your local master branch regularly
+will keep it up-to-date. You will also want to rebase your feature branches
+up-to-date as well. After pulling, you can checkout the feature branches
+and rebase them:
git checkout master
+git pull upstream master --ff-only # to make certain there are no merge commits
+git rebase master feature_branch
+git push --force-with-lease # (set origin to be the same as local)
+
+To avoid merges as per the No-Merge Policy, you may want to use
+git config pull.ff only
(this will apply the config only to the local repo)
+to ensure that Git doesn't create merge commits when git pull
ing, without
+needing to pass --ff-only
or --rebase
every time.
You can also git push --force-with-lease
from master to keep your fork's master in sync with
+upstream.
If your branch contains multiple consecutive rewrites of the same code, or if
+the rebase conflicts are extremely severe, you can use
+git rebase --interactive master
to gain more control over the process. This
+allows you to choose to skip commits, edit the commits that you do not skip,
+change the order in which they are applied, or "squash" them into each other.
Alternatively, you can sacrifice the commit history like this:
+# squash all the changes into one commit so you only have to worry about conflicts once
+git rebase -i $(git merge-base master HEAD) # and squash all changes along the way
+git rebase master
+# fix all merge conflicts
+git rebase --continue
+
+"Squashing" commits into each other causes them to be merged into a single +commit. Both the upside and downside of this is that it simplifies the history. +On the one hand, you lose track of the steps in which changes were made, but +the history becomes easier to work with.
+You also may want to squash just the last few commits together, possibly
+because they only represent "fixups" and not real changes. For example,
+git rebase --interactive HEAD~2
will allow you to edit the two commits only.
git range-diff
After completing a rebase, and before pushing up your changes, you may want to
+review the changes between your old branch and your new one. You can do that
+with git range-diff master @{upstream} HEAD
.
The first argument to range-diff
, master
in this case, is the base revision
+that you're comparing your old and new branch against. The second argument is
+the old version of your branch; in this case, @upstream
means the version that
+you've pushed to GitHub, which is the same as what people will see in your pull
+request. Finally, the third argument to range-diff
is the new version of
+your branch; in this case, it is HEAD
, which is the commit that is currently
+checked-out in your local repo.
Note that you can also use the equivalent, abbreviated form git range-diff master @{u} HEAD
.
Unlike in regular Git diffs, you'll see a -
or +
next to another -
or +
+in the range-diff output. The marker on the left indicates a change between the
+old branch and the new branch, and the marker on the right indicates a change
+you've committed. So, you can think of a range-diff as a "diff of diffs" since
+it shows you the differences between your old diff and your new diff.
Here's an example of git range-diff
output (taken from Git's
+docs):
-: ------- > 1: 0ddba11 Prepare for the inevitable!
+1: c0debee = 2: cab005e Add a helpful message at the start
+2: f00dbal ! 3: decafe1 Describe a bug
+ @@ -1,3 +1,3 @@
+ Author: A U Thor <author@example.com>
+
+ -TODO: Describe a bug
+ +Describe a bug
+ @@ -324,5 +324,6
+ This is expected.
+
+ -+What is unexpected is that it will also crash.
+ ++Unexpectedly, it also crashes. This is a bug, and the jury is
+ ++still out there how to fix it best. See ticket #314 for details.
+
+ Contact
+3: bedead < -: ------- TO-UNDO
+
+(Note that git range-diff
output in your terminal will probably be easier to
+read than in this example because it will have colors.)
Another feature of git range-diff
is that, unlike git diff
, it will also
+diff commit messages. This feature can be useful when amending several commit
+messages so you can make sure you changed the right parts.
git range-diff
is a very useful command, but note that it can take some time
+to get used to its output format. You may also find Git's documentation on the
+command useful, especially their "Examples" section.
The rust-lang/rust repo uses what is known as a "rebase workflow." This means
+that merge commits in PRs are not accepted. As a result, if you are running
+git merge
locally, chances are good that you should be rebasing instead. Of
+course, this is not always true; if your merge will just be a fast-forward,
+like the merges that git pull
usually performs, then no merge commit is
+created and you have nothing to worry about. Running git config merge.ff only
+(this will apply the config to the local repo)
+once will ensure that all the merges you perform are of this type, so that you
+cannot make a mistake.
There are a number of reasons for this decision and like all others, it is a +tradeoff. The main advantage is the generally linear commit history. This +greatly simplifies bisecting and makes the history and commit log much easier +to follow and understand.
+NOTE: This section is for reviewing PRs, not authoring them.
+Github has a button for disabling whitespace changes that may be useful.
+You can also use git diff -w origin/master
to view changes locally.
To checkout PRs locally, you can use git fetch upstream pull/NNNNN/head && git checkout FETCH_HEAD
.
You can also use github's cli tool. Github shows a button on PRs where you can copy-paste the +command to check it out locally. See https://cli.github.com/ for more info.
+ +Git and Github's default diff view for large moves within a file is quite poor; it will show each +line as deleted and each line as added, forcing you to compare each line yourself. Git has an option +to show moved lines in a different color:
+git log -p --color-moved=dimmed-zebra --color-moved-ws=allow-indentation-change
+
+See the docs for --color-moved
for more info.
See the relevant section for PR authors. This can be useful for comparing code +that was force-pushed to make sure there are no unexpected changes.
+Many large files in the repo are autogenerated. To view a diff that ignores changes to those files, +you can use the following syntax (e.g. Cargo.lock):
+git log -p ':!Cargo.lock'
+
+Arbitrary patterns are supported (e.g. :!compiler/*
). Patterns use the same syntax as
+.gitignore
, with :
prepended to indicate a pattern.
NOTE: submodules are a nice thing to know about, but it isn't an absolute
+prerequisite to contribute to rustc
. If you are using Git for the first time,
+you might want to get used to the main concepts of Git before reading this section.
The rust-lang/rust
repository uses Git submodules as a way to use other
+Rust projects from within the rust
repo. Examples include Rust's fork of
+llvm-project
, cargo
and libraries like stdarch
and backtrace
.
Those projects are developed and maintained in an separate Git (and GitHub)
+repository, and they have their own Git history/commits, issue tracker and PRs.
+Submodules allow us to create some sort of embedded sub-repository inside the
+rust
repository and use them like they were directories in the rust
repository.
Take llvm-project
for example. llvm-project
is maintained in the rust-lang/llvm-project
+repository, but it is used in rust-lang/rust
by the compiler for code generation and
+optimization. We bring it in rust
as a submodule, in the src/llvm-project
folder.
The contents of submodules are ignored by Git: submodules are in some sense isolated
+from the rest of the repository. However, if you try to cd src/llvm-project
and then
+run git status
:
HEAD detached at 9567f08afc943
+nothing to commit, working tree clean
+
+As far as git is concerned, you are no longer in the rust
repo, but in the llvm-project
repo.
+You will notice that we are in "detached HEAD" state, i.e. not on a branch but on a
+particular commit.
This is because, like any dependency, we want to be able to control which version to use.
+Submodules allow us to do just that: every submodule is "pinned" to a certain
+commit, which doesn't change unless modified manually. If you use git checkout <commit>
+in the llvm-project
directory and go back to the rust
directory, you can stage this
+change like any other, e.g. by running git add src/llvm-project
. (Note that if
+you don't stage the change to commit, then you run the risk that running
+x
will just undo your change by switching back to the previous commit when
+it automatically "updates" the submodules.)
This version selection is usually done by the maintainers of the project, and +looks like this.
+Git submodules take some time to get used to, so don't worry if it isn't perfectly +clear yet. You will rarely have to use them directly and, again, you don't need +to know everything about submodules to contribute to Rust. Just know that they +exist and that they correspond to some sort of embedded subrepository dependency +that Git can nicely and fairly conveniently handle for us.
+Sometimes you might run into (when you run git status
)
Changes not staged for commit:
+ (use "git add <file>..." to update what will be committed)
+ (use "git restore <file>..." to discard changes in working directory)
+ (commit or discard the untracked or modified content in submodules)
+ modified: src/llvm-project (new commits, modified content)
+
+and when you try to run git submodule update
it breaks horribly with errors like
error: RPC failed; curl 92 HTTP/2 stream 7 was not closed cleanly: CANCEL (err 8)
+error: 2782 bytes of body are still expected
+fetch-pack: unexpected disconnect while reading sideband packet
+fatal: early EOF
+fatal: fetch-pack: invalid index-pack output
+fatal: Fetched in submodule path 'src/llvm-project', but it did not contain 5a5152f653959d14d68613a3a8a033fb65eec021. Direct fetching of that commit failed.
+
+If you see (new commits, modified content)
you can run
$ git submodule foreach git reset --hard
+
+and then try git submodule update
again.
git blame
Some commits contain large reformatting changes that don't otherwise change functionality. They can
+be instructed to be ignored by git blame
through
+.git-blame-ignore-revs
:
git blame
to use .git-blame-ignore-revs
as the list of commits to ignore: git config blame.ignorerevsfile .git-blame-ignore-revs
git blame
.Please include a comment for the commit that you add to .git-blame-ignore-revs
so people can
+easily figure out why a commit is ignored.
This chapter gives an overview of how Edition support works in rustc. +This assumes that you are familiar with what Editions are (see the Edition Guide).
+The --edition
CLI flag specifies the edition to use for a crate.
+This can be accessed from Session::edition
.
+There are convenience functions like Session::at_least_rust_2021
for checking the crate's
+edition, though you should be careful about whether you check the global session or the span, see
+Edition hygiene below.
As an alternative to the at_least_rust_20xx
convenience methods, the Edition
type also
+supports comparisons for doing range checks, such as span.edition() >= Edition::Edition2021
.
Adding a new edition mainly involves adding a variant to the Edition
enum and then fixing
+everything that is broken. See #94461 for an
+example.
The Edition
enum defines whether or not an edition is stable.
+If it is not stable, then the -Zunstable-options
CLI option must be passed to enable it.
When adding a new feature, there are two options you can choose for how to handle stability with a +future edition:
+span.at_least_rust_20xx()
(see Edition hygiene) or the
+Session::edition
. This will implicitly depend on the stability of the edition itself to
+indicate that your feature is available.It may be sufficient to only check the current edition for relatively simple changes. +However, for larger language changes, you should consider creating a feature gate. +There are several benefits to using a feature gate:
+#![feature(…)]
attribute is used that your new feature is
+being enabled.When a feature is complete and ready, the feature gate can be removed (and the code should just
+check the span or Session
edition to determine if it is enabled).
There are a few different options for doing feature checks:
+For highly experimental features, that may or may not be involved in an edition, they can
+implement regular feature gates like tcx.features().my_feature
, and ignore editions for the time
+being.
For experimental features that might be involved in an edition, they should implement gates with
+tcx.features().my_feature && span.at_least_rust_20xx()
.
+This requires the user to still specify #![feature(my_feature)]
, to avoid disrupting testing of
+other edition features which are ready and have been accepted within the edition.
For experimental features that have graduated to definitely be part of an edition,
+they should implement gates with tcx.features().my_feature || span.at_least_rust_20xx()
,
+or just remove the feature check altogether and just check span.at_least_rust_20xx()
.
If you need to do the feature gating in multiple places, consider placing the check in a single +function so that there will only be a single place to update. For example:
+// An example from Edition 2021 disjoint closure captures.
+
+fn enable_precise_capture(tcx: TyCtxt<'_>, span: Span) -> bool {
+ tcx.features().capture_disjoint_fields || span.rust_2021()
+}
+
+See Lints and stability below for more information about how lints handle +stability.
+For the most part, the lexer is edition-agnostic.
+Within StringReader
, tokens can be modified based on edition-specific behavior.
+For example, C-String literals like c"foo"
are split into multiple tokens in editions before 2021.
+This is also where things like reserved prefixes are handled for the 2021 edition.
Edition-specific parsing is relatively rare. One example is async fn
which checks the span of the
+token to determine if it is the 2015 edition, and emits an error in that case.
+This can only be done if the syntax was already invalid.
If you need to do edition checking in the parser, you will normally want to look at the edition of
+the token, see Edition hygiene.
+In some rare cases you may instead need to check the global edition from ParseSess::edition
.
Most edition-specific parsing behavior is handled with migration lints instead of in the parser.
+This is appropriate when there is a change in syntax (as opposed to new syntax).
+This allows the old syntax to continue to work on previous editions.
+The lint then checks for the change in behavior.
+On older editions, the lint pass should emit the migration lint to help with migrating to new
+editions.
+On newer editions, your code should emit a hard error with emit_err
instead.
+For example, the deprecated start...end
pattern syntax emits the
+ellipsis_inclusive_range_patterns
lint on editions before 2021, and in 2021 is an hard error via
+the emit_err
method.
New keywords can be introduced across an edition boundary.
+This is implemented by functions like Symbol::is_used_keyword_conditional
, which rely on the
+ordering of how the keywords are defined.
When new keywords are introduced, the keyword_idents
lint should be updated so that automatic
+migrations can transition code that might be using the keyword as an identifier (see
+KeywordIdents
).
+An alternative to consider is to implement the keyword as a weak keyword if the position it is used
+is sufficient to distinguish it.
An additional option to consider is the k#
prefix which was introduced in RFC 3101.
+This allows the use of a keyword in editions before the edition where the keyword is introduced.
+This is currently not implemented.
Spans are marked with the edition of the crate that the span came from. +See Macro hygiene in the Edition Guide for a user-centric description of what this means.
+You should normally use the edition from the token span instead of looking at the global Session
+edition.
+For example, use span.edition().at_least_rust_2021()
instead of sess.at_least_rust_2021()
.
+This helps ensure that macros behave correctly when used across crates.
Lints support a few different options for interacting with editions. +Lints can be future incompatible edition migration lints, which are used to support +migrations to newer editions. +Alternatively, lints can be edition-specific, where they change their +default level starting in a specific edition.
+Migration lints are used to migrate projects from one edition to the next.
+They are implemented with a MachineApplicable
suggestion which
+will rewrite code so that it will successfully compile in both the previous and the next
+edition.
+For example, the keyword_idents
lint will take identifiers that conflict with a new keyword to
+use the raw identifier syntax to avoid the conflict (for example changing async
to r#async
).
Migration lints must be declared with the FutureIncompatibilityReason::EditionError
or
+FutureIncompatibilityReason::EditionSemanticsChange
future-incompatible
+option in the lint declaration:
declare_lint! {
+ pub KEYWORD_IDENTS,
+ Allow,
+ "detects edition keywords being used as an identifier",
+ @future_incompatible = FutureIncompatibleInfo {
+ reason: FutureIncompatibilityReason::EditionError(Edition::Edition2018),
+ reference: "issue #49716 <https://github.com/rust-lang/rust/issues/49716>",
+ };
+}
+
+When declared like this, the lint is automatically added to the appropriate
+rust-20xx-compatibility
lint group.
+When a user runs cargo fix --edition
, cargo will pass the --force-warn rust-20xx-compatibility
+flag to force all of these lints to appear during the edition migration.
+Cargo also passes --cap-lints=allow
so that no other lints interfere with the edition migration.
Migration lints can be either Allow
or Warn
by default.
+If it is Allow
, users usually won't see this warning unless they are doing an edition migration
+manually or there is a problem during the migration.
+Most migration lints are Allow
.
If it is Warn
by default, users on all editions will see this warning.
+Only use Warn
if you think it is important for everyone to be aware of the change, and to
+encourage people to update their code on all editions.
+Beware that new warn-by-default lint that hit many projects can be very disruptive and frustrating
+for users.
+You may consider switching an Allow
to Warn
several years after the edition stabilizes.
+This will only show up for the relatively small number of stragglers who have not updated to the new
+edition.
Lints can be marked so that they have a different level starting in a specific edition.
+In the lint declaration, use the @edition
marker:
declare_lint! {
+ pub SOME_LINT_NAME,
+ Allow,
+ "my lint description",
+ @edition Edition2024 => Warn;
+}
+
+Here, SOME_LINT_NAME
defaults to Allow
on all editions before 2024, and then becomes Warn
+afterwards.
This should generally be used sparingly, as there are other options:
+Small impact stylistic changes unrelated to an edition can just make the lint Warn
on all
+editions. If you want people to adopt a different way to write things, then go ahead and commit to
+having it show up for all projects.
Beware that if a new warn-by-default lint hits many projects, it can be very disruptive and +frustrating for users.
+Change the new style to be a hard error in the new edition, and use a migration lint to
+automatically convert projects to the new style. For example,
+ellipsis_inclusive_range_patterns
is a hard error in 2021, and warns in all previous editions.
Beware that these cannot be added after the edition stabilizes.
+Migration lints can also change over time.
+For example, the migration lint can start out as Allow
by default.
+For people performing the migration, they will automatically get updated to the new code.
+Then, after some years, the lint can be made to Warn
in previous editions.
For example anonymous_parameters
was a 2018 Edition migration lint (and a hard-error in 2018)
+that was Allow
by default in previous editions.
+Then, three years later, it was changed to Warn
for all previous editions, so that all users got
+a warning that the style was being phased out.
+If this was a warning from the start, it would have impacted many projects and be very disruptive.
+By making it part of the edition, most users eventually updated to the new edition and were
+handled by the migration.
+Switching to Warn
only impacted a few stragglers who did not update.
Lints can be marked as being unstable, which can be helpful when developing a new edition feature, +and you want to test out a migration lint. +The feature gate can be specified in the lint's declaration like this:
+declare_lint! {
+ pub SOME_LINT_NAME,
+ Allow,
+ "my cool lint",
+ @feature_gate = sym::my_feature_name;
+}
+
+Then, the lint will only fire if the user has the appropriate #![feature(my_feature_name)]
.
+Just beware that when it comes time to do crater runs testing the migration that the feature gate
+will need to be removed.
Alternatively, you can implement an allow-by-default migration lint for an upcoming unstable +edition without a feature gate. +Although users may technically be able to enable the lint before the edition is stabilized, most +will not notice the new lint exists, and it should not disrupt anything or cause any breakage.
+In the 2018 edition, there was a concept of "idiom lints" under the rust-2018-idioms
lint group.
+The concept was to have new idiomatic styles under a different lint group separate from the forced
+migrations under the rust-2018-compatibility
lint group, giving some flexibility as to how people
+opt-in to certain edition changes.
Overall this approach did not seem to work very well, +and it is unlikely that we will use the idiom groups in the future.
+Each edition comes with a specific prelude of the standard library.
+These are implemented as regular modules in core::prelude
and std::prelude
.
+New items can be added to the prelude, just beware that this can conflict with user's pre-existing
+code.
+Usually a migration lint should be used to migrate existing code to avoid the conflict.
+For example, rust_2021_prelude_collisions
is used to handle the collisions with the new traits
+in 2021.
Usually it is not possible to make breaking changes to the standard library. +In some rare cases, the teams may decide that the behavior change is important enough to break this +rule. +The downside is that this requires special handling in the compiler to be able to distinguish when +the old and new signatures or behaviors should be used.
+One example is the change in method resolution for into_iter()
of arrays.
+This was implemented with the #[rustc_skip_array_during_method_dispatch]
attribute on the
+IntoIterator
trait which then tells the compiler to consider an alternate trait resolution choice
+based on the edition.
Another example is the panic!
macro changes.
+This required defining multiple panic macros, and having the built-in panic macro implementation
+determine the appropriate way to expand it.
+This also included the non_fmt_panics
migration lint to adjust old code to the new form, which
+required the rustc_diagnostic_item
attribute to detect the usage of the panic macro.
In general it is recommended to avoid these special cases except for very high value situations.
+ +Use the -Z unpretty=hir
flag to produce a human-readable representation of the HIR.
+For cargo projects this can be done with cargo rustc -- -Z unpretty=hir
.
+This output is useful when you need to see at a glance how your code was desugared and transformed
+during AST lowering.
For a full Debug
dump of the data in the HIR, use the -Z unpretty=hir-tree
flag.
+This may be useful when you need to see the full structure of the HIR from the perspective of the
+compiler.
If you are trying to correlate NodeId
s or DefId
s with source code, the
+-Z unpretty=expanded,identified
flag may be useful.
TODO: anything else? #1159
+ +