3.7 Programming Languages
Every language we add is one more set of tools, habits and dependencies to keep healthy. We therefore keep the list short and pick each language for a kind of work, not for a project.
3.7.1 Which Language for What¶
| Kind of work | Language | Why |
|---|---|---|
| Internal critical systems: backends, workers, the farm sync client | Rust | Typed, memory-safe and fast; errors the compiler can find never reach production |
| Graphical interfaces, native and web | Rust with Slint | One language and one toolchain from the backend to the screen |
| Machine learning, internal tooling, scripts, prototypes | Python | The language of the machine learning ecosystem; quick to write |
| Simple web sites, and public web apps that people reach from search engines | TypeScript with Nuxt | The common tools of the web: pages that search engines index, accessible and quick to load |
A prototype may be written in Python. When it becomes a critical system, it is rewritten in Rust rather than hardened in place.
3.7.2 Toolchains¶
- The toolchain version is pinned in
rust-toolchain.tomlat the repository root. cargo fmt --checkandcargo clippy -- -D warningsrun in CI; a warning fails the build.- Dependencies are reviewed before they are added, and
Cargo.lockis committed for applications.
- Projects and virtual environments are managed with uv; the Python version is pinned in
.python-version. ruff format --checkandruff checkrun in CI.- Public functions carry type hints.
3.7.3 Slint¶
Slint draws the interface itself, the same way on every platform, which is what lets one codebase serve desktop and web.
- License. We use Slint under its royalty-free license for desktop, mobile and web applications. The license requires an attribution: the
AboutSlintwidget in an About screen (or the splash screen), or the Slint badge on a public page of the application. - Web. On the web, Slint runs as WebAssembly drawing on a canvas, which search engines cannot index and which must be checked for accessibility, text selection and load time. The Organization Dashboard is built with Slint (decided 2026-11-04): its users are signed in and work in it every day, so search indexing does not matter to it. A public app that people find through search is built with Nuxt.
3.7.4 Nuxt¶
Nuxt is for simple web sites, and for public web apps that people reach from search engines. The first site is the News site at news.moonitoring.com, a static site served from Cloudflare and kept in its own repository, moonitoring-app-news, apart from the backend of news and from the website; the first app is MooGrant, the web app of grant, which farmers open as guests from a search before they have an account.
- When. The site publishes content and shares no code, rules or data with a Rust system; or the app is public, and search indexing, accessibility, text selection and the first load decide whether people use it, which are the points Slint on the web has still to prove (see Slint); or the app is where people sign in, register and accept invitations, the Account app, whose forms must work with the browser's autofill and password managers.
- A Nuxt app is an interface like any other: it owns no data and decides no business rule, and it calls the systems' APIs with the same contracts and tokens as the other apps. Its repository is
moonitoring-app-<name>(see Repository Naming). - When not. A critical system stays in Rust. An app used by signed-in people at work, such as the Organization Dashboard, stays in Rust with Slint.
3.7.5 Writing Rust¶
We use what makes Rust worth choosing: the compiler proves ownership, lifetimes and the shape of data, so we write code that lets it (decided 2026-11-10). The rules below come on top of cargo clippy, whose verdict still wins.
Features: build only what is used¶
- Every dependency names the features it needs, starting from
default-features = false, in[workspace.dependencies]of the repository's rootCargo.toml; a part adds features where it uses them. A default feature set is accepted only when the crate cannot be used without it, and the line says why. - Our own library crates put each optional part behind a feature (for example the gRPC server, the Dev ORM, a cloud client), so a consumer compiles only what it calls. Features only add: a feature never changes the behavior of code that does not use it, never removes an item, and builds alone and with every other feature (
cargo hack check --each-featurewhere the crate has several). - No unused dependency:
cargo macheteruns with the checks, andcargo tree -e featuresis read when a dependency is added, so a feature enabled by one part does not silently grow another. - One version of each dependency in a workspace where it can be helped (
cargo tree -d), and TLS through rustls only.
Ownership and lifetimes¶
- Borrow by default. A function that only reads takes
&str,&[T],&Torimpl AsRef<…>; it takes ownership only when it keeps the value. A struct that only reads for a short scope holds a reference with a lifetime instead of an owned copy;Cow<'_, T>when a value is sometimes borrowed and sometimes built. - A
.clone()is a decision, not a way to silence the borrow checker: cloning a cheap handle (anArc, a pool, a client) is fine; cloning data to escape a lifetime is a design smell to fix. Arconly for ownership that is really shared between tasks or threads,Arc<str>orArc<[T]>for shared immutable data; noRc<RefCell<…>>outside interface code that needs it, no'staticbound orBox::leakto make a lifetime go away.- Resources end with their scope (RAII): transactions, locks, temporary files, spans and connections are guards that release on
Drop; a lock guard is never held across an.await, and a blocking call never runs on the async runtime (spawn_blocking). - Tasks have an owner: a spawned task is joined, cancelled through a token, or tracked by a
JoinSet; shutdown waits for them with a deadline.
Types that say what they mean¶
- Newtypes for identifiers, units and validated values (
OrganizationId(Uuid),Bytes(u64)), built throughTryFromor a constructor that checks; enums instead of booleans and stringly typed states;#[must_use]on results and builders that must not be dropped;#[non_exhaustive]on public enums and structs of a library that will grow. - Typestate where an object passes through states with different operations (a builder that cannot build until its required fields are set, a release that cannot be approved before it is staged): the wrong call does not compile.
- Iterators over index loops,
constandconst fnfor what is known at compile time,impl Traitin argument and return position before boxing, generics beforedynunless the set of types is open. - Errors are typed (
thiserrorin libraries, an error enum per use case); nounwraporexpecton anything that comes from outside the process, andunsafestays denied by the workspace lints.