Welcome. This course begins by making the project rebuildable, rather than merely buildable on one development machine. Before we define narrative state or create a Godot controller, we need a compatibility contract for the three moving parts that compile and run the game: the Godot editor/runtime, godot-rust, and Rust itself.
For this project, that contract is:
- Godot runtime/editor: exactly 4.7.1
- Rust bindings: exactly
godotcrate 0.5.4 fromgodot-rust - Generated API level:
api-4-7 - Rust language edition: 2024
- Rust compiler: exactly 1.85.0
- Dependency resolution: committed
Cargo.lock, always respected in CI and release builds
The rest of this module will turn that fixed foundation into a workspace with clean dependency boundaries, but this lesson establishes the policy that prevents a future editor update or a casual cargo update from changing the engine beneath your content.
A version number is not one compatibility decision
It is tempting to say “we use Godot 4.7.1” and treat that as sufficient. In a GDExtension project, it is not. There are several layers with distinct roles:
| Layer | What is pinned | Why |
|---|---|---|
| Godot executable | 4.7.1 editor and runtime artifact | Determines the actual engine that loads and runs your extension. |
godot-rust crate | godot = "=0.5.4" | Determines Rust-side bindings, macros, generated types, and extension behavior. |
| Binding API feature | api-4-7 | Selects the statically generated Godot 4.7 API surface. |
| Rust compiler | 1.85.0 | Determines language features, diagnostics, build behavior, and generated native code. |
| Cargo dependency graph | Cargo.lock | Freezes all direct and transitive crate resolutions. |
The important distinction is between the Godot runtime version and the godot-rust API level.
api-4-7 means that Rust code is generated against the Godot 4.7 API line, whose baseline is 4.7.0. This is appropriate for a project that deliberately runs Godot 4.7.1: patch releases within the same minor version are intended to remain compatible, while the extension retains a stable, statically checked 4.7 API surface.
Do not select a hypothetical patch-level feature merely because the editor is 4.7.1. Patch-level API selection is for the unusual case where a patch introduces an API your extension must call. This engine should instead regard 4.7.1 as the required runtime, while using the standard api-4-7 binding level.
Selecting a Godot version - The godot-rust book
Read the relevant sections of “Selecting a Godot version” in the godot-rust book. It explains why the binding API level is a compatibility choice rather than simply a restatement of the editor version.
In the subsection “Cutting edge vs. compatibility,” read the compatibility tradeoff. Then read the subsections “Default version,” “Lower minor version,” and “Lower or higher patch version.” Focus on feature selection: explicitly selecting an API feature protects the project when a later godot-rust release changes its default API line.
For Godot 4.7 support specifically, the selected godot-rust release matters. The project should pin godot 0.5.4, whose changelog adds the api-4-7 level.
gdext/Changelog.md at master · godot-rust/gdext · GitHub
Read the v0.5.4 release entry in the godot-rust changelog. This is the compatibility evidence for selecting the binding release rather than following the repository’s moving master branch.
In the “v0.5.4” entry, begin with the Godot 4.7 support note. The key decision is that this release introduces the API level the project needs; the other listed features are not requirements for this initial pinning step.
One implication follows immediately: do not depend on godot-rust through a floating Git branch for this engine. A Git dependency on master makes a rebuild depend on the date it happens. A released, exact Cargo version plus a committed lockfile is the more suitable production baseline.
Pin the Rust side in the workspace root
Place the shared policy in the root Cargo.toml, rather than letting individual crates pick potentially incompatible godot versions or API features.
The workspace will gain its actual crate responsibilities in the next lesson. For now, the relevant root configuration should look like this:
[workspace]
members = [
"crates/vn_engine",
"crates/vn_content",
"crates/vn_godot",
]
resolver = "3"
[workspace.package]
edition = "2024"
rust-version = "1.85"
[workspace.dependencies]
godot = { version = "=0.5.4", features = ["api-4-7"] }
There are several deliberate choices here.
Exact crate version
The leading = is significant:
version = "=0.5.4"
Without it, a normal Cargo requirement such as "0.5.4" permits compatible future patch releases. Those releases may be entirely valid, but accepting them should be a deliberate upgrade, reviewed alongside Godot compatibility and test results.
The Godot-facing crate will later opt into this centrally declared dependency:
[dependencies]
godot = { workspace = true }
This makes the version and API-level policy visible in one location. It also prevents accidental activation of a second, mutually exclusive api-* feature elsewhere in the workspace.
Explicit API feature
The godot-rust book notes that only one api-* feature may be active. Writing api-4-7 explicitly avoids relying on godot-rust’s default API level, which can change when the bindings begin targeting a newer Godot minor release.
For this project, the desired static API contract is therefore:
features = ["api-4-7"]
Avoid api-custom for the production baseline. That mode generates bindings from a locally selected Godot binary and may require LLVM and bindgen; more importantly, its documentation does not offer the same compatibility guarantees. It is useful for experimental custom Godot builds, not for a project whose stated target is the standard 4.7.1 release.
Edition and minimum supported compiler
godot-rust’s change notes include support for Rust Edition 2024.
gdext/Changelog.md at master · godot-rust/gdext · GitHub
Scan the toolchain portion of the changelog to confirm the language-edition context for the workspace configuration.
In the toolchain bullet list, read the edition note. The workspace will standardize on Edition 2024 rather than allowing crates to choose editions independently.
edition = "2024" is a source-language choice. rust-version = "1.85" declares the minimum compiler release that packages in the workspace support. Neither one, by itself, forces contributors to use the same compiler patch release. That is the job of rust-toolchain.toml.
Pin the actual compiler, not the stable channel
At the repository root, add:
# rust-toolchain.toml
[toolchain]
channel = "1.85.0"
profile = "minimal"
components = ["rustfmt", "clippy"]
This file tells rustup to select Rust 1.85.0 whenever a developer works inside this repository. It also makes formatting and linting available from the same pinned toolchain.
The distinction is worth preserving:
rust-toolchain.tomlfixes the compiler used for local development and CI.rust-version = "1.85"communicates the minimum compiler expected by published or reusable workspace packages.edition = "2024"selects the Rust language edition.
Do not use channel = "stable" here. “Stable” is a moving label, so it turns a clean checkout six months from now into a different build environment.
For this course, Rust 1.85.0 is the conservative fixed baseline for Edition 2024. If a later, intentional dependency upgrade requires a newer compiler, update both the toolchain file and workspace.package.rust-version in the same reviewed change, then rerun the complete test and export matrix.
Finally, generate and commit the lockfile:
cargo generate-lockfile
After that point, normal validation commands should use --locked:
cargo build --workspace --locked
cargo test --workspace --locked
Cargo.lock freezes the full resolved crate graph, including transitive dependencies and registry checksums. It is not redundant with the exact godot version: the exact version protects the binding crate itself, while the lockfile protects everything it depends on.
Treat the Godot executable as an approved build artifact
Cargo cannot pin the Godot editor for you. project.godot describes a project, but it is not an editor-version lockfile. Likewise, the generated .godot/ directory is cache data, not a reproducibility mechanism.
Keep a small, committed tool manifest under version control, for example tools/godot/godot.toml. Its exact archive names and hashes will vary by operating system and architecture, but its shape should record all of these facts:
[godot]
version = "4.7.1"
channel = "stable"
[[artifact]]
target = "linux-x86_64"
archive = "approved Godot 4.7.1 archive filename"
sha256 = "approved archive SHA-256 digest"
executable = "approved executable path inside the archive"
[[artifact]]
target = "windows-x86_64"
archive = "approved Godot 4.7.1 archive filename"
sha256 = "approved archive SHA-256 digest"
executable = "approved executable path inside the archive"
This is a schema, not a template to leave with placeholder values. Populate it with the real, approved archive filename and SHA-256 digest for every platform used by developers or CI.
A provisioning step should then:
- Select the artifact for the current host platform.
- Obtain that exact artifact from your approved distribution source or CI cache.
- Verify its SHA-256 digest before extraction.
- Run the extracted executable with its version-reporting flag.
- Reject the environment unless the reported Godot version is 4.7.1.
This policy has practical value beyond CI. It prevents one contributor from silently opening the project with 4.7.2 or a development build, importing assets differently, and committing changed project metadata or resources that another contributor cannot reproduce.
The Godot binary may live outside Git because it is large, but the identity of the binary must live in Git through the manifest and its checksums.
Make drift visible early
A pinned configuration is useful only if the project detects deviations. Add these checks to the repository’s initial developer and CI workflow:
| Check | Expected result |
|---|---|
rustup show active-toolchain | Rust 1.85.0 is active in the repository. |
cargo tree -e features -p godot | Resolves godot 0.5.4 and includes api-4-7. |
cargo build --workspace --locked | Builds without changing Cargo.lock. |
| Godot version command through the approved path | Reports 4.7.1. |
git diff -- Cargo.lock after a normal build | No lockfile changes. |
A few rules keep these checks meaningful:
- Commit
Cargo.lockfor this application and engine workspace. - Review any
Cargo.toml,Cargo.lock,rust-toolchain.toml, or Godot manifest change as a compatibility change. - Never accept a lockfile update incidentally because an unrelated feature was being developed.
- Keep the selected Godot executable path configurable by environment or a repository script, but verify its version and checksum against the committed manifest.
- Do not mix
api-4-7with anotherapi-*feature anywhere in the dependency graph.
At this point, the project has a precise definition of “the same build”: same Godot 4.7.1 artifact, same Rust 1.85.0 toolchain, same godot-rust 0.5.4 bindings, same 4.7 API level, and same Cargo dependency graph.
Key takeaways
- Pin the runtime editor to Godot 4.7.1, but pin godot-rust’s generated API to the 4.7 line with
api-4-7. - Use
godot = "=0.5.4"rather than a floating Git revision or a permissive Cargo requirement. - Use both
rust-toolchain.tomlwith Rust 1.85.0 and workspace-level Edition 2024 /rust-versionsettings. - Commit
Cargo.lockand run builds with--locked. - Record and verify the Godot binary artifact separately; Cargo does not control the editor version.
Next, we will use this fixed toolchain to partition the workspace into the pure Rust engine, compiled Rust content, the Godot adapter, and presentation assets—without allowing Godot APIs to leak into the simulation or authored game logic.
Can't find a good explanation? Sign up and we'll make it for you
Sign up