fix(sandbox): compile rust as edition 2021
Summary
Rust was already registered, but rustc compiled every submission as edition 2015 because no --edition was passed. Rust now compiles as edition 2021 by default, and a caller's compiler_flags: ["--edition=2018"] replaces that default instead of being added as a second --edition.
Motivation
Closes #117
#117 asks for Rust (edition 2021) on POST /api/v1/execute. The language entry, Docker package and API enum were already there, but without --edition rustc uses 2015, where TryFrom, TryInto and FromIterator are not in the prelude. Ordinary solutions such as let x: u8 = n.try_into()? failed with error[E0599]: no method named 'try_into' found, which a user can't explain because the same code compiles on any contest site.
Implementation notes
LangConfig.CompileDefaults(new,internal/sandbox/language.go): flags appended afterCompileArgsunless a caller flag has the same name (the text before=). Rust setsCompileDefaults: ["--edition=2021"]; every other language leaves it empty, so its compile command is unchanged.- Why not just append
--edition=2021toCompileArgs:AllowedCompilerFlagsalready lets callers send--edition=2018/--edition=2021, and rustc rejects a repeated option (error: Option 'edition' given more than once). The default has to be replaceable, not stacked. -Ostays inCompileArgs: rustc accepts-Otwice (verified with rustc 1.85.1), so a caller-Owas never a problem.- The rule lives in the language table, not the worker, so the worker stays language-agnostic.
- Compile cache: keys are language + source + files + flags, held in memory, so nothing compiled under the old args survives a deploy.
- Limits unchanged: in the real sandbox, a single-file program using generics and std collections compiles in 1.5 s / 76 MB, well inside the 20 s / 512 MB compile budget.
Verified against the real sandbox
I built docker/go-judge and sent commands made by the engine's own BuildCompileCmdWithOptions / BuildRunCmd (a throwaway program, not committed):
| Case | Compile args (after rustc) | Result |
|---|---|---|
try_into, no flags | -O -o /w/a.out /w/main.rs --edition=2021 | Accepted, 0.53 s / 57 MB, prints 42 |
compiler_flags: ["--edition=2018"] | … --edition=2018 (one edition) | E0599 as expected for 2018, so the caller's edition is the one used |
compiler_flags: ["-O"] | … --edition=2021 -O | Accepted, prints 255 |
| generics + collections | … --edition=2021 | Accepted, 1.49 s / 76 MB |
| type error | … --edition=2021 | Nonzero exit, rustc's error[E0308] kept in stderr |
Before this change, the first case fails to compile with E0599.
Questions for the reviewer
Q1 — What existing tests cover the code you changed?
A: TestValidateCompilerFlags (which flags are allowed for rust), TestProbeLanguages and TestLanguageAvailability (rust's binaries come from CompileArgs[0] / RunArgs, which are unchanged), the TestCompileCache* tests (compile sharing and key, which this doesn't change), and the TestEffective* / TestBuildRunCmd* tests in internal/sandbox. All pass unchanged.
Q2 — What new tests did you add, and what cases do they cover?
A: internal/sandbox/language_test.go: TestBuildCompileCmdRustDefaults (no flags → --edition=2021; caller --edition=2018 replaces it; caller --edition=2021 isn't repeated; caller -O keeps the default edition), TestBuildCompileCmdWithoutDefaults (C++ compile args unchanged), and TestBuildCompileCmdNeverRepeatsAllowedFlag (for every language with defaults, no allowed caller flag can repeat one). internal/worker/rust_test.go uses a fake go-judge (httptest, no real sandbox runs): a Rust job compiles main.rs with --edition=2021 and runs the cached binary for every test; a caller edition replaces the default; a compile failure comes back with rustc's diagnostics.
Q3 — Does this change affect any authenticated or unauthenticated API paths? If yes, how?
A: No new paths, fields or validation. POST /api/v1/execute with language: "rust" now compiles as edition 2021; the request schema and the compiler_flags allowlist are unchanged, and it behaves the same for authenticated and unauthenticated callers.
Q4 — Did you run golangci-lint run locally and get 0 issues?
A: Yes. golangci-lint v2.15.0 reports 0 issues. (run in the golangci/golangci-lint:v2.15.0 image on an LF checkout of the commit).
Q5 — Is this the only open PR you have against development?
A: Yes.
Checklist
-
go test -race -count=1 -timeout=120s ./...passes locally (in agolang:1.27.2container;internal/dbneeds testcontainers and runs in CI) -
golangci-lint run --timeout=5mreports 0 issues -
gofmt -s -l .reports no unformatted files - All five questions above are answered (not left as
…) - No secrets or credentials in code / config
- New dependencies reviewed and justified (none added;
go mod tidyis clean) - Related issue linked in "Closes #" above
Summary by cubic
Fixes Rust submissions failing to compile as edition 2021 instead of rustc's default 2015, so solutions using try_into, TryFrom, or FromIterator no longer fail with error[E0599]. Closes #117.
- Compile defaults were added to
LangConfig: defaults apply unless a caller flag with the same name overrides them. - A caller's
--edition=2018replaces the default instead of stacking, which rustc rejects; other languages' compile args are unchanged.
Summary by CodeRabbit
- New Features
- Rust compilation now defaults to the 2021 edition. You can select another edition, such as 2018, through compiler flags; the selected edition replaces the default rather than being added alongside it.
- Documentation
- The supported-languages table now lists Rust compiler version 1.85 and its default optimization and edition flags. The changelog also describes the default edition and how to override it.