Specifying the edition in the source file

Split from Idea: rustc & cargo should warn on unspecified edition - #17 by zackw

1 Like

How (im)practical would it be to make Cargo look into the crate root .rs file for this source attribute?

I think cargo could probably get a "good enough" idea by just peeking at the source, but if that were to be truly accurate and robust, it would have to parse enough to resolve stuff like cfg_attr.

1 Like

The edition is allowed to affect tokenization, and you need to tokenize in order to get at the attributes. Requiring attributes in order to determine the edition would cause a chicken-and-egg problem.

Moderator note: Also, this has been split into a separate topic.

10 Likes

Generally, the edition affects some Cargo features too, but otherwise a source attribute seems fine.

1 Like

This is true, but I think it isn't necessarily a showstopper.

rustc already runs the tokeniser speculatively (and discards the results) when it's deciding whether !# at the start of a file is a shebang or the start of an attribute.

Doing that in a special "unknown edition" mode might be thinkable.

3 Likes

Both of these things suggest to me that the hypothetical #![rust_edition] attribute needs to be a special case, in much the same way that <meta charset> is a special case in HTML: it's going to have to be processed by a special-purpose parser that doesn't handle the full generality of the language, and there are going to be unusual restrictions on how you can use it. Restrictions something like:

  • It can only appear in the crate root
  • It has to be the very first sequence of tokens in the crate root after processing comments (note that this means it has to appear before doc comments for the entire crate, as those notionally get converted to doc attributes).
  • It has to be used as a #![] attribute, i.e. it has to be applied to the entire crate
  • It cannot be used via #![cfg_attr] or any other mechanism for conditional compilation

This means that the only new capability we would gain is the ability to specify an edition in the crate root .rs file rather than in Cargo.toml. Right now, unless I've missed something, it's not possible to conditionalize the edition of a crate at all. The edition key in Cargo.toml takes a constant, and Cargo.toml has no generic conditional facility. (Does anyone have a concrete reason to want to be able to conditionalize the edition of their crate?)

That may mean that it's not worth the code it takes to implement. I think the strongest argument in favor of the feature is that it would streamline passing test programs around a little: #![rust_edition(2021)] at the top of the file, with automatic effect, instead of // hey human reading this file, make sure to use --edition 2021. It is also shorter and arguably easier to read in cargo scripts: borrowing the example from the top of the cargo script RFC, we could have

#!/usr/bin/env cargo
#![rust_edition(2021)]
//! ```cargo
//! [dependencies]
//! clap = { version = "4.2", features = ["derive"] }
//! ```

instead of

#!/usr/bin/env cargo
//! ```cargo
//! [package]
//! edition = 2021
//! [dependencies]
//! clap = { version = "4.2", features = ["derive"] }
//! ```

But that's not enough of an improvement that I would advocate for the feature solely on that basis.

2 Likes

Also note that example could be written as

#!/usr/bin/env cargo
//! ```cargo
//! package.edition = 2021
//! [dependencies]
//! clap = { version = "4.2", features = ["derive"] }
//! ```

meaning moving the edition to a top-level attribute doesn't even reduce line count.

1 Like

Note that the currently proposed cargo script syntax (which has been proposed for FCP) is

#!/usr/bin/env cargo

---
[dependencies]
clap = { version = "4.2", features = ["derive"] }
---

use clap::Parser;

#[derive(Parser, Debug)]
#[clap(version)]
struct Args {
    #[clap(short, long, help = "Path to config")]
    config: Option<std::path::PathBuf>,
}

fn main() {
    let args = Args::parse();
    println!("{:?}", args);
}

See RFC: Syntax for embedding cargo-script manifests by epage · Pull Request #3503 · rust-lang/rfcs · GitHub

One of the elements that went into the design for this was to avoid cargo and other tools from having to parse Rust code. Another element was that we didn't want to special case syntax elements to only be valid in very restricted contexts, like a type of attribute that can't be specified like other attributes.

4 Likes

I like the concept of having the .rs file specify the edition. And if rustc were the only thing that cared about the edition, I'd say that that would be completely fine.

However, cargo also cares about the edition, and uses that to determine some aspects of its behavior, which may affect among other things how it invokes rustc. So there's definitely a chicken-and-egg problem in how cargo would have to obtain the edition.

2 Likes

And you'd have to consider what happens if both are specified with conflicting values. If I say edition 2021 in Cargo.toml but 2018 in the rust file, what should happen? If cargo passes --edition to rustc it likely means using the Cargo.toml value unless there is extra logic causing a compile error in that case.

I think disagreement between --edition and what's specified in the code should be a deny-by-default lint. In the majority of cases this will be a simple mistake: someone wanted to adjust the crate's edition, did so in Cargo.toml, didn't realize it was also specified in lib.rs. Or vice versa. In the more exotic situations where one might want --edition to win (e.g. debugging edition-related bugs in rustc itself), one can set the lint to allowed.

We could make it a deny-by-default lint to even encounter the edition attribute when called from cargo, to minimize the likelihood of the problem arising of these getting out of sync in the first place for most people.