Hyphen vs underscore in binary name

[On Windows, using Rust 1.73.0]

I just noticed that cargo build --all-targets produces two binaries if the example name has hyphens in it. (I guess they could be hard linked, but this is on Windows and they don't make it obvious to find out of that is the case).

I'm building an example called foo-bar. cargo produces foo-bar.exe, but it also produces a foo_bar.exe, and (the thing that bit me) foo_bar.pdb (instead of the expected foo-bar.pdb).

Is this expected behavior?

Rust doesn't allow crates with names like foo-bar, because the hyphen is not a valid character in an identifier, and crate names are identifiers. This was a slightly contentious issue many years ago, because people liked using - in crate names ('cause it looks good), which created a ton of crates that all had to be manually renamed every single time to actually use them. Which sucked.

The solution was to make hyphens a lie: they're actually just underscores in a pair of platform shoes and a hat. So when you use a crate called foo-bar, it appears in code as foo_bar because that's what it's "real" name is.

But... if you call a binary crate foo-bar, then you damn well expect the executable to be called foo-bar. My guess is that Rust is compiling the crate as foo_bar, and then making a copy of the executable to give it the name you expect it to have. foo-bar.exe and deps/foo_bar.exe are byte-for-byte identical.

So I'd say it's less-than-optimal, but expected behaviour.

13 Likes

that's not the only reason. hyphens are treated as word delimiters in common languages, especially outside of the programming realm. (file names or urls, for example, often use hyphens instead underscores). quote from google's developer style document :

Search engines interpret hyphens in file and directory names as spaces between words. Underscores are generally not recognized, meaning that their presence can negatively affect SEO.

interestingly enough, in some programming languages (most notably the lisp family, where minus is used as a regular function instead of binary operator), hyphen is a valid identifier character. Clojure is an interesting case because it targets JVM so hypen-to-underscore conversion is necessary when generating the byte code.

cargo is in a mixed state (both for practical reason, and backward compatibility: rfc 940). packages are allowed to have names with hyphens, and crates inside packages can be named using hyphens too! here the terminology might be different from how people casually use them: packages are the units with Cargo.toml that cargo manages (resolves dependency tree, unifiy feature flags, downloads source code, etc), crates are compilation units for rustc invocation.

it's only the [lib] target name is restricted to valid rust identifiers, because when used as a dependency, the name is imported into rust code as namespace identifier of the extern crate. other target names like [[bin]], [[test]], [[example]] don't have to be valid rust identifiers (well, actually cargo will do the hyphen-to-underscore conversion automatically).

quote cargo book about target naming:

The name field specifies the name of the target, which corresponds to the filename of the artifact that will be generated. For a library, this is the crate name that dependencies will use to reference it. For the [lib] and the default binary (src/main.rs), this defaults to the name of the package, with any dashes replaced with underscores. For other auto discovered targets, it defaults to the directory or file name.

that's not entirely accurate. as already pointed out, cargo first builds [[bin]] targets using names that has been converted to underscore, and then makes a copy of the final exe file. rustc is not informed about final artifact file name, so the pdb file is generated for the "underscore-ified" name.

cargo converts the name fields to underscore and passes it to rustc as --crate-name. as for the exe and pdb outputs, I think cargo can do better. you can, in fact, use the -o compiler flag to tell rustc to generate artifact with different name other than the crate name. for example, suppose a file foo.rs contains:

#![crate_name="underscore_only"]
fn main() {
	println!("hello world!");
}

you can compile it like this:

Y:\>rem default output use crate name: underscore_only.exe underscore_only.pdb
Y:\>rustc foo.rs

Y:\>rem overwrite with -o: hello-world.exe hello-world.pdb
Y:\>rustc -o hello-world.exe foo.rs

Y:\>rem specify --crate-name given same value
Y:\>rustc -o hello-world.exe --crate-name underscore_only foo.rs

Y:\>rem overwrite --crate-name only works if source code has no `#[crate_name]` attribute
Y:\>rustc -o hello-world.exe --crate-name different_from_attribute foo.rs
error: `--crate-name` and `#[crate_name]` are required to match, but `different_from_attribute` != `underscore_only`
 --> foo.rs:1:1
  |
1 | #![crate_name="underscore_only"]
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error


Y:\>
6 Likes

Indeed. On linux it creates foo_bar-6775aaf8c5049cb3 which is hard linked to foo-bar.

The thing that got me is that in the Cargo.toml I clearly stated that I want foo-bar.exe (by calling the example foo-bar), so I would have expected a foo-bar.pdb.

IMO, whatever post-processing step that is giving us foo-bar.exe should give us foo-bar.pdb as well.

The only reason I'm got a little cranky about this is that it took me an embarrassingly long time to figure out why it wasn't able to copy out the pdb file when I could clearly see it was there .. not noticing it was using _ and not -. :grimacing:

Anyway, knowing that hyphens in binary names are faked as well is helpful, and it explains the behavior.

My understanding is that the PDB name is baked into the executable, so copying the PDB to a new name won't do anything. Since foo-bar.exe is just a copy of foo_bar.exe, it will still be looking for foo_bar.pdb.

What cargo needs to do is tell rustc to generate foo-bar.exe directly.

I once spent my entire lunch break trying to fix the sound on the library computer. I tried everything. Fiddled with the volume mixer, uninstalled and reinstalled the sound hardware. Uninstalled and reinstalled the drivers. Checked every program for conflicts. Rebooted repeatedly. Nothing worked.

Right before lunch was over, I finally found the problem.

The speakers weren't turned on.

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.