Skip to content

gen-rust emits the math builtins as bare free functions, which Rust does not have #3400

Description

@gHashTag

The Rust backend already knows how to translate every math builtin, and never gets the chance to.

The defect

zig_builtin_to_rust maps the whole family correctly — "@min" if two => format!("({}).min({})", ...), "@sqrt" | "@abs" | "@round" | ... => format!("({}).{}()", ...). Its first line is:

fn zig_builtin_to_rust(name: &str, args: &[String]) -> Option<String> {
    if !name.starts_with(char::from(64u8)) {   // no `@` sigil
        return None;
    }

Specs write the builtins bareabs(x), min(a, b) — so the table never answers, and the bare name is emitted verbatim. Rust has none of these as free functions, and rustc replies:

error[E0425]: cannot find function `abs` in this scope

The Zig backend closed this identical class at gen_expr, with a comment naming the same symptom ("use of undeclared identifier abs") and a declared_fns guard. The Rust backend has neither.

Measured

  • 118 of 650 specs contain a call-shaped occurrence of one of the seven; 99 of those parse.
  • Corpus-wide rustc acceptance of the emitted Rust is 429 of 650 (instrument: rustc --edition 2021 --crate-type lib --crate-name g --emit=metadata, with a control that compiles a valid file and rejects an invalid one — an earlier run of this measurement read 0 of 650 because the filenames carried .t27 and rustc derives the crate name from the filename, so nothing was ever compiled).

Three traps in the obvious repair

Each was measured, not anticipated:

  1. A spec may own the name. 30 declarations across the corpus give one of these names to a spec function — fn floor in 10 specs, fn abs in 9, fn cos in 3. Redirecting those to a Rust method is a wrong translation that compiles, which is strictly worse than a bare name that does not. 3 of the 30 declare their return type Zig-style without ->, so a guard built on collect_fn_ret_types (which skips an empty extra_return_type) would be narrower than its subject exactly where it matters.

  2. A literal receiver has no type. (5.0).sqrt() is error[E0689]: can not call method sqrt on ambiguous numeric type {float}, and so is ((2.0 / 3.141592653589793)).sqrt() — the first version of the guard tested for a single literal token and still admitted the compound form.

  3. A const initialiser can never work. sqrt is not a const fn; inside pub const K: f64 = ... every spelling fails. Trading E0425 for E0015 there is a new failure, not a repair.

Separately: @log is mapped wrongly

The existing table has "@log" ... => format!("({}).{}()", ...), emitting (x).log(). Rust’s f64::log takes a base argument, so that is error[E0061]: this method takes 1 argument but 0 arguments were supplied. The natural log is ln. Filed here rather than changed under an unrelated title.

What the repair is worth

Honestly: corpus-wide rustc acceptance moves 429 → 429, a delta of zero. The emitted Rust of 9 files changes and none crosses from fail to pass — each carries other blockers, matching the repository’s established shape that a rejected file has four or five error families, not one.

The value is not the corpus number. It is that a clean spec can now produce Rust that compiles at all, which is the precondition for expressing hand-written code as specs. A worked example is in the PR: a spec port of rings/ring-103-rust/src/lib.rs whose generated Rust agrees with the hand-written original bit-exactly on 20,736 inputs, including NaN, both infinities, both zeros and subnormals.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions