(zilch lang rust): document

Change-Id: I6a6a6964c8aaff8d5f3e18bc5c7486746b5a2952
This commit is contained in:
puck 2025-06-23 12:22:20 +00:00
parent ae774da043
commit 0340f6e830
10 changed files with 597 additions and 82 deletions

View file

@ -0,0 +1,179 @@
= Zilch's Rust support
:page-pagination:
Zilch supports using the Rust compiler directly from Scheme. This can be used
to compile single crates, or to drive the crate support more directly.
== Compiling Rust code
To compile Rust code, xref:generated:zilch.lang.rust.adoc[`(zilch lang rust)`]
provides a `call-rustc` procedure, which handles most of the code necessary to
compile Rust code.
[,scheme,line-comment=;]
----
(define gcc (cdr (assoc "out" (nixpkgs "gcc")))) ; <1>
(define outputs
(call-rustc
(zfile "fn main() { println!(\"Hello, World!\"); }") ; <2>
'() ; <3>
crate-type: 'bin
crate-name: "hello"
edition: "2021"
emits: '(dep-info: #t link: #t) ; <4>
codegen-flags: (cons "linker" #~,(string-append #$gcc "/bin/cc")))) ; <5>
;; (("dep-info"
;; . #<store path /0fnqikhh1rkw991hd37lzxhk13w5zzcs770jvn629d9mrs0zb6ax
;; (ca~ /nix/store/x1g5mjj7py2r263ma5314phfpcwa4gg7-rustc-bin-hello.drv!dep-info)>)
;; ("link"
;; . #<store path /05lmqcyk0kazll2i1jcp7299pfgxnxj0hw32hllxkb1iqxs4fqbm
;; (ca~ /nix/store/x1g5mjj7py2r263ma5314phfpcwa4gg7-rustc-bin-hello.drv!link)>))
----
<1> Get a copy of the `out` output for `gcc` in nixpkgs.
<2> Use a file defined inline to compile.
<3> Don't add any variables to the environment.
<4> Make `rustc` only emit the `dep-info` and `link` outputs.
<5> Tell `rustc` where to find the linker.
The resulting binary is linked dynamically, using the linker passed either as
`codegen-flag`, or findable as `cc` on `$PATH`. By default, each `emits:` entry
ends up as a separate output; they are all part of the same derivation, though.
To compile more than one file, a more involved structure has to be used; Rust
expects the file structure to exist on-disk, and `mod` works relative to the
directory the current file is in.
[,scheme,line-comment=;]
----
(define multi-file
(zdir
"lib.rs" (zfile "pub mod foo;")
"foo.rs" (zfile "pub fn hello(who: String) { println!(\"Hello, {who}\"); }")))
(define outputs
(call-rustc
#~,(string-append #$multi-file "/lib.rs") ; <1>
'()
crate-type: 'rlib
crate-name: "hello-lib"
edition: "2021"
emits: '(metadata: #t link: #t)))
----
<1> Import `multi-file` (the directory) to store, append `/lib.rs` to its store
path, then use that as the file input to `rustc`. This construction is
equivalent to `"${./multi-file}/lib.rs"` in Nix.
== Reading Cargo files
Cargo files contain a lot of automated detection of paths. For example, a
binary can live at `src/bin.rs`, or at `src/bins/foo.rs`. To handle this, Zilch
requires the Cargo file be accompanied with a VFS representing the crate. To
handle workspaces, parsing a `Cargo.toml` takes a workspace, and returns both
the crate _and_ workspace defined in the file, where applicable. This is
handled by xref:generated:zilch.lang.rust.cargo.adoc[`parse-cargo-toml`].
Reading a `Cargo.lock` is handled by `parse-lockfile`, which takes in the
contents of a lockfile, and parses it into a list of lockfile entries. Each
entry in the lockfile can be used in xref:generated:zilch.lang.rust.registry.adoc[`fetch-and-unpack-crate`]
to fetch the crate, though this is usually done automatically by the resolver.
== The resolver
The rust resolver in Zilch handles selecting dependencies from a `Cargo.lock`
for one or more Cargo targets.
To use it, first parse a lockfile and a `Cargo.toml`:
[,scheme,line-comment=;]
----
(define example-vfs (vfs-from-directory "example"))
(define lockfile
(parse-lockfile
(read-file "example/Cargo.lock"))) <1>
(define-values (crate workspace)
(parse-cargo-toml
example-vfs
(read-file "example/Cargo.toml")
#f))
----
<1> `read-file` is an example procedure, and isn't part of Zilch (yet).
If desired, add more `Cargo.toml` files to prefer over any crates found in the
`Cargo.lock`:
[,scheme,line-comment=;]
----
(define override-vfs (vfs-from-directory "override-example"))
(define-values (override-crate workspace)
(parse-cargo-toml
override-vfs
(read-file "override-example/Cargo.toml")
#f))
----
Then, call the resolver:
[,scheme,line-comment=;]
----
(define targets
(process-many-with-lockfile
(list
(cons crate example-vfs)
(cons override-crate override-vfs))
lockfile))
----
The result from `process-many-with-lockfile` is a list of `<resolved-package>`
records, each representing a Cargo target in the crates that have been
processed.
[CAUTION]
====
By default, the resolver will set _no_ features on any crate, not even default
ones. As exception, if a binary crate is available, it _will_ have the default
feature set, which is likely to percolate down to most other crates it depends
on. There is currently no way to change this behavior easily.
====
When a target is to be built, it can be used with `build-package`:
[,scheme,line-comment=;]
----
(define (build-script-env-override-example crate-name is-dependency)
(if (and is-dependency (string=? crate-name "test")) ; <1>
(list
(cons
"PATH"
#~,(string-append
#$(cdr (assoc "out" (nixpkgs "hello")))
"/bin")))
#f))
(define (compiler-env-override-example crate-name is-dependency)
(if (and (not is-dependency) (string=? crate-name "test")) ; <2>
'(("HELLO_STRING" . "Hello!"))
#f))
(map
(lambda (pkg)
(build-package
pkg
build-script-env-override-example
compiler-env-overridde-example)) ; <3>
targets)
----
<1> Add GNU Hello to the PATH of any build script that depends on the crate `test`
<2> If the crate `test` is built, set `HELLO_STRING`. This can be read from
Rust using the `env!` macro.
<3> Use the overrides when building each crate's targets.
[#limits]
=== Limitations
The `(zilch lang rust resolver)` implements most of the logic of a https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions[v1 resolver],
which handles selecting dependencies from a set of candidates. The biggest
difference between Zilch's resolver and the canonical implemention (in Cargo)
is that the resolver in Zilch will never fall back to looking for a candidate
inside any configured Cargo registries. If a crate isn't found in the `Cargo.lock`
it is considered an error. While this makes using Zilch a tad more cumbersome,
it means that Zilch itself is entirely deterministic on the files of the crates
it processes.