Skip to content

rust2-go exports pub constants and statics - #645

Draft
kixelated wants to merge 3 commits into
mainfrom
quest/m4/go/consts
Draft

kixelated wants to merge 3 commits into
mainfrom
quest/m4/go/consts

Conversation

@kixelated

@kixelated kixelated commented Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Finishes quest/m4/go/consts.md, planned in moq-dev/rust2#630 from moq-dev/rust2#614's follow-ups. rust2-go now exports every pub constant and static, associated constants included, instead of refusing each at its span, so a crate that has any translates with no config.

  • Constants are evaluated once by rustc (const_eval_poly) and lowered through lower/consts.rs, the same path an operand's value takes.
    • An integer, bool, char, or &str constant becomes a Go const (const MaxLen = uint8(200)). A &str becomes an untyped Go string.
    • Any other constant becomes a Go function returning its value (func Origin() TalliesTotal), so each use gets a fresh value.
  • Statics are now translated, not just exported. Each static is one package-level Go variable, declared at its first use with the value rustc evaluated. Translated code and Go callers share it.
    • A reference to a static, from a function body or a constant, is &Var.
    • A slice into a static's array is that variable sliced (Letters[1:4]), so it aliases the static as Rust's does.
    • A static whose value reaches back to itself, such as two statics pointing at each other, is declared without a value and set in an init(), because Go rejects a variable whose initializer refers to it. Nested allocations inside a static are read as constant memory.
  • Names: each upper-case word of the constant's or static's own name is camel cased: MAX_LEN becomes MaxLen, Counter::MAX becomes CounterMax. The owner path keeps moq-dev/rust2#614's rule.
  • Still refused at their span: a static mut, a static of a type with interior mutability (atomics), a thread-local or foreign static, an associated constant of a generic type, and a reference into part of a static (&S.1).
  • Constant memory a static or constant points to can also be a slice of any element type now, such as static TABLE: &[u16]. Before, only &str was read from memory.

Fx::new builds the lowering context, and a constant or static root lowers in an empty MIR body. This is how a root reaches const_value outside any function. Statics have no cross-crate MIR to borrow a body from.

Choices made without asking

  • A non-basic constant is a Go function, not a var. The quest recommended a var. The OpenAI review on #630 asked that a constant stay a fresh value at each use while a static is shared storage. With a var, one Go caller's items.Origin.Sum = 9, or a method called on &items.Origin, would change what later Go callers read. A function makes that impossible. The cost is call syntax: items.Origin().
  • char counts as a Go basic type beside integers, bool, and &str: const Letter = rune(233).
  • A static of a type that isn't Freeze is refused outright. No interior-mutable type translates today, and a static one is always Sync, meaning atomics or locks. So "interior mutability translation refuses" reads as every !Freeze static.
  • Go callers can still write a static, or through what a constant or static points to. Go has no read-only variable. The README says Go callers must not do this.

Tests

  • fixtures/items gains pub constants of every basic type, i64::MIN included, plus non-basic constants and associated ones. It also gains pub statics: a struct, a &[u16], a &str, an array, and a slice of part of another static. Two private statics form a cycle, and a dependency crate contributes a constant and a static.
  • Its differential case checks translated code against native Rust on all of these: a constant's copy changed leaves the next use alone, a static has one address, a constant pointing at a static points at it, the ring is walked, and an index past a static's end panics.
  • New tests/exports/main.go reads the exports from Go, and fixtures.rs compares its output with native values.
    • It binds the basic constants with Go const, so the build fails if any of them is not a Go constant.
    • It changes Origin() and CountStart() results and shows the next call is fresh.
    • It writes Hits and shows translated Run reads the write.
    • It writes Letters and shows Tail sees it.
  • fixtures/reject loses its pub const case and gains each refusal above.

Public API

What a translation exports. Crates with pub constants or statics now translate. Before, each was a compile error. Nothing that translated before changes.

Caused by

None: moq-dev/rust2#614 refused these on purpose as new codegen.

Validation

Suggested follow-ups

  • A reference into part of a static or constant allocation (const SECOND: &u8 = &HALVES.1), as &Var.F1. It is refused, as a second reference into constant memory at another offset already is. Recommendation: drop. No consumer needs it, and quest/m4/go/coverage.md already tracks the constant-memory case.

Models

Size: M
Implementer: Claude Code, claude-opus-5-5, high effort

Review

Rewritten by quest review after each round.

(Written by Claude Opus 5.5 with high effort)

🤖 Generated with Claude Code

Quest-Claim: ae01a44aeb894132
@pullfrog

pullfrog Bot commented Nov 7, 2026

Copy link
Copy Markdown

Pullfrog  | Review this ➔

kixelated and others added 2 commits November 7, 2026 00:31
A `pub` constant of an integer, `bool`, `char`, or `&str` is a Go `const`;
any other is a function returning a fresh value at each call, as each use of
Rust's is. A static is one Go variable that translated code and Go callers
share, assigned in an `init` when its value reaches back to it. Upper-case
names are camel cased per word (`MAX_LEN` as `MaxLen`). A `static mut`, a
static with interior mutability, a foreign or thread-local static, a generic
associated constant, and a reference into part of a static stay refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant