How does the rust compiler handle multiple compiler versions when compiling a project

I have specified rust edition2021 in my local project in cargo.toml, but the third-party libraries introduced in the project are specified in cargo.toml to be compiled with rust edition2015 and rust edition2018, and my current version of rust is the latest version of edition 2021. So how does rust automatically handle the compilation of library code by different compilers during the compilation process? Is there a lower version of rust compiler in the latest rust compiler for internal auto selection? Is there any information available on this?
Can you help me to answer this question?

2 Likes

They are not different compilers -- a given version of the Rust toolchain can handle all editions up to that point, and crates with different choices of edition are still interoperable.

See also: Editions do not split the ecosystem

7 Likes

I checked the link you provided, but it still doesn't address the question I asked, it is too vague in its explanation of this piece; another thing is that I don't understand what you mean by the text explanation. Can you explain it again?

1 Like

It's all the same compiler.

You can think of the compiler as doing this:

match edition {
  Edition2015 => compile_with_2015_syntax_and_rules(),
  Edition2018 => compile_with_2018_syntax_and_rules(),
  Edition2018 => compile_with_2021_syntax_and_rules(),
}
3 Likes

How does it actually handle it? Can you explain it in detail?

Meta-discussion: it's kinda matter of perspective. I guess @danielesk expects detailed explanation of how one compiler may handle many dialects but that's not something people would write in a blogposts.

I mean: would you expect a blogpost which explains why you need to use wheel to turn car around and pedal to speed it up? Well… maybe on some historians web site, but Rust is too young for that.

I would say that compilers for most popular languages are doing what rustc is goind. There are no real difference between ristc --edition 2018, gfortran --std f2008, or javac -version 11 thus that part is not considered important enough to separately discuss.

Now, the ability to actually link code compiled in different modes together… that one is a tiny bit unusual and is worth discussing.

2 Likes

What exactly is not clear? There are just one rustc compiler on your system (just like there are, most likely, one C++ compiler or one Java compiler). It has --edition option which specifies version of Rust to use (like gcc have --std option and javac have -version option). Then cargo specifies it when crate is compiled and thus rustc knows how to treat your code.

Can you explain what precisely is unclear in that, really trivial, process? It's really just a comparison of some numbers in the compiler which directs it to treat certain constructs differently. In C++, Java or Rust, they all employ the same approach.

3 Likes

The differences between editions are mostly superficial. Once a crate is translated to the internal representation, the rest of code generation is nearly the same, and the differences that remain are handled by an edition flag for the code's origin. Calling conventions, type layouts, etc. are all identical.

Maybe the RFCs will have the detail you want?

https://rust-lang.github.io/rfcs/2052-epochs.html
https://rust-lang.github.io/rfcs/3085-edition-2021.html

3 Likes

Editions can be mixed even in the same file, even within a single line of code (through macros), so every expression has an associated edition property with it.

In the code implementing the rustc compiler, in a few places where this matters, there are checks like if expr.edition() == Edition2015 { bla bla } else { turbo bla bla }.

Rust editions are not like C or C++ versions. They are more like extern "C" block in C++ source code. It's still the same compiler compiling essentially the same code, just some syntax rules are temporarily a bit different.

7 Likes

I understand a little bit through your answer, but I still have a few questions to ask you.

  1. you just mentioned that the rust version is not like the c or c++ version, so what is the c or c++ version?
  2. about your explanation I understand this, you see whether it is correct: we start with edition 2015, when edition 2015 is released, it means that the basic syntax structure of rust is basically stable, but in the subsequent editions released may be more or less add some features, and these features may destroy the previous ( edition 2015), this time rust in a similar if expr.edition() == Edition2015 { bla bla } else { turbo bla bla } for compatibility (compatible with the current edition, while preparing for the subsequent editions to be released), and then release the new edition; this means that the basic syntax structure of rust is basically stable. Then the new edition is released; it's a bit like patching (the same unchanging syntax rules structure is not changed, only the differences and similarities are handled).

What I don't quite understand is that there are different editions in the project (edition 2015 edition 2018 edition 2021), how does Rust automatically handle the compilation of the library code by different compilers during the compilation process? My initial understanding is that the latest version of the compiler covers all versions of the previous compiler, and then compile according to the version of the edition specified in cargo.toml, rust at this time internal judgment version to automatically call the corresponding compiler to compile, I found that this understanding is incorrect.

Not all versions, but all editions, yes. What forced you to treat this as "incorrect"?

1 Like

What I mean is that the high version compiler covers all the low version compilers, and all the code between versions is independent without common parts, for a simple example: when rustc starts compiling, it first determines the edition, and then all the subsequent operations are done only in this edition (from lexical analysis to LLVM IR generation)

It doesn't "determine" the edition - the edition is one of the arguments, but otherwise that's correct. Well, except the macros, which are compiled in the context of the definition crate, not the usage one (that is, they might have different edition being set).

I still don't quite understand it, can you explain it in detail from beginning to end?

This is just implementation detail. Users can't really differentiate these two implementations

fn rustc_impl_1(edition) {
   match edition {
     2015 => {
        common_1();
        something_specific_to_2015();
        common_2();
     },
     2018 => {
        common_1();
        something_specific_to_2018();
        common_2();
     },
     2021 => {
        common_1();
        something_specific_to_2021();
        common_2();
     },
   }
}

fn rustc_impl_2(edition) {
  common_1();
  match edition {
    2015 => something_specific_to_2015(),
    2018 => something_specific_to_2018(),
    2021 => something_specific_to_2021(),
  }  
  common_2();
}


What does this phrase mean?

  1. C and C++ have versions named after year of release, e.g. C99, C++17. Because Rust editions have a year too, some people assume it uses similar versioning mechanism, which is not true.

  2. No, features available in Rust Edition 2015 have changed and keep changing every 6 weeks. All Rust editions will continue to evolve together for as long as Rust exists. New features are added to Rust 2015 all the time, because it has all of the features of the latest version of Rust, except support for some keywords and other minor details that were the reason for making Edition 2018. Edition 2018 also keeps changing and getting all new features of Rust, where possible.

Rust editions aren't versions of the language. Rust doesn't have past frozen-in-time versions of the language. You can write code in Edition 2015 and use features that were added in 2023 and didn't exist back in the year 2015.

Editions are just tiny tiny tweaks to parsing of the latest version of Rust (which changes) for backwards compatibility with old source code. For example, when parsing code in the Edition 2015 compilation mode, async is allowed to be a variable name. When edition is set to something else, it's a keyword, and is not allowed in variable names any more unless you spell it r#async.

5 Likes

In short, think of editions like a transpiler, like babel for JavaScript, except a default and nonoptional part of the toolchain. There's some "edition agnostic" dialect of Rust, and the compiler knows how to translate each edition to that shared dialect.

If you're familiar with how the JVM (e.g. Java, Kotlin, Scala, etc) or CLR (e.g. C#, F#, etc) allow multiple languages to interoperate nearly seamlessly, Rust works essentially the same way. There's some extra details to the edition system that allow mixing editions at a hyper-granular (per token) level, enabled by the limited scope of edition changes, but the analogy stands. The internal bytecode for Rust is called MIR.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.