EEE

The systemd project has officially introduced support for developing programs in Rust, following the merger of a pull request submitted by Linux kernel developer Christian Brauner. The change, merged into the project’s main branch, establishes a new development option for systemd while keeping its existing C-based architecture intact.

And before anyone assumes systemd is preparing a complete rewrite in Rust, that’s not the case. The maintainers agreed on a deliberately limited approach: Rust may be used only for new, standalone components that other parts of systemd do not depend on. Existing C code will remain untouched.

Another important decision concerns the build process. Rather than introducing Cargo, Rust’s standard package manager and build tool, systemd continues to use Meson for all builds. This keeps Rust development integrated into the project’s existing build infrastructure.

The implementation also places strict limits on dependencies. Rust programs use the no_std approach, meaning they do not rely on Rust’s standard library or external third-party crates. Instead, they interact with systemd’s existing C libraries through generated bindings.

For memory management, systemd provides its own Rust implementations of common container types, with allocation failures handled explicitly rather than causing the program to abort. The new infrastructure also includes wrappers for logging, command-line argument handling, event processing, and error management, allowing Rust utilities to behave consistently with their C counterparts.

Rust support is optional and automatically detected during configuration. Developers wishing to enable it need Rust 1.85 or later, bindgen 0.71.1 or later, and Meson 1.4 or later. Systems without the required toolchain can continue building systemd without Rust support.

Interestingly, initial tests show that a stripped Rust command-line utility occupies about 32 KB, compared to 20 KB for its C equivalent built with link-time optimization. This gives an early indication of the additional overhead, though the comparison involves only a small test program.

The first planned real-world application of this infrastructure is systemd-keyring-setup, a new utility intended to help manage certificates in Linux kernel keyrings. Its implementation is being developed separately, alongside additional Rust wrappers for systemd’s shared libraries.

For Linux distributions, the change introduces packaging considerations. Builds that enable all available features will need the appropriate Rust compiler and bindgen dependencies or must disable Rust support to avoid build failures.

Whether Rust becomes widely used within systemd will depend on future development, but the necessary groundwork is now in place.

    • urushitan 漆たん@kakera.kintsugi.moe
      link
      fedilink
      arrow-up
      1
      arrow-down
      1
      ·
      2 days ago

      ok a makefile is basically a script runner like this. the c/c++ compiler doesn’t touch the makefile, that’s a separate tool

      edit : main.o kbd.o command.o display.o \
             insert.o search.o files.o utils.o
              cc -o edit main.o kbd.o command.o display.o \
                         insert.o search.o files.o utils.o
      
      main.o : main.c defs.h
              cc -c main.c
      kbd.o : kbd.c defs.h command.h
              cc -c kbd.c
      command.o : command.c defs.h command.h
              cc -c command.c
      display.o : display.c defs.h buffer.h
              cc -c display.c
      insert.o : insert.c defs.h buffer.h
              cc -c insert.c
      search.o : search.c defs.h buffer.h
              cc -c search.c
      files.o : files.c defs.h buffer.h command.h
              cc -c files.c
      utils.o : utils.c defs.h
              cc -c utils.c
      clean :
              rm edit main.o kbd.o command.o display.o \
                 insert.o search.o files.o utils.o
      

      for a rust program, you might have a cargo.toml like

      [package]
      name = "jsontool"
      version = "0.1.0"
      edition = "2021"
      
      [dependencies]
      anyhow = "1"
      clap = { version = "4", features = ["derive"] }
      serde = { version = "1", features = ["derive"] }
      serde_json = "1"
      
      [profile.release]
      lto = true
      

      and .cargo/config.toml like (for the local distro config, no internet packages)

      [source.crates-io]
      replace-with = "distro"
      
      [source.distro]
      directory = "/usr/share/cargo/registry"   # or: registry = "sparse+https://crates.internal.example/index/"
      
      [net]
      offline = true
      

      If you wanted that same functionality from a makefile for rust it would be something like

      # Makefile: build jsontool with plain rustc, no cargo.
      RUSTC  ?= rustc
      REG    := /usr/share/cargo/registry
      OUT    := $(abspath build)
      HOST   := $(shell $(RUSTC) -vV | sed -n 's/^host: //p')
      FLAGS  := -C opt-level=3 --cap-lints=allow -L dependency=$(OUT) --out-dir $(OUT)
      ext     = $(foreach c,$(1),--extern $(c)=$(OUT)/lib$(c).rlib)
      extpm   = $(foreach c,$(1),--extern $(c)=$(OUT)/lib$(c).so)
      
      # Pinned by hand. Cargo.lock does this for you; here, a distro upgrade
      # renames these directories and the build breaks.
      UNICODE_IDENT := $(REG)/unicode-ident-1.0.13
      PROC_MACRO2   := $(REG)/proc-macro2-1.0.89
      QUOTE         := $(REG)/quote-1.0.37
      SYN           := $(REG)/syn-2.0.85
      SERDE_DERIVE  := $(REG)/serde_derive-1.0.213
      # ... ~20 more ...
      
      # ---- 1. Plain library crate ----
      # Edition, crate name (hyphens -> underscores), and features all come from
      # reading each crate's Cargo.toml yourself.
      $(OUT)/libunicode_ident.rlib:
      	$(RUSTC) --edition=2018 --crate-type=lib --crate-name unicode_ident \
      	    $(FLAGS) $(UNICODE_IDENT)/src/lib.rs
      
      # ---- 2. Crate with a build.rs: compile it, run it with the env vars cargo
      #         would set, then scrape its stdout for cfg flags ----
      $(OUT)/proc_macro2.cfg:
      	mkdir -p $(OUT)/bs-pm2
      	$(RUSTC) --edition=2021 --crate-type=bin $(PROC_MACRO2)/build.rs \
      	    -o $(OUT)/bs-pm2/build-script
      	cd $(PROC_MACRO2) && env OUT_DIR=$(OUT)/bs-pm2 RUSTC=$(RUSTC) \
      	    TARGET=$(HOST) HOST=$(HOST) PROFILE=release OPT_LEVEL=3 \
      	    CARGO_MANIFEST_DIR=$(PROC_MACRO2) CARGO_PKG_VERSION=1.0.89 \
      	    CARGO_FEATURE_DEFAULT=1 CARGO_FEATURE_PROC_MACRO=1 \
      	    CARGO_CFG_TARGET_OS=linux CARGO_CFG_TARGET_ARCH=x86_64 \
      	    $(OUT)/bs-pm2/build-script \
      	  | sed -n 's/^cargo::\?rustc-cfg=\(.*\)/--cfg=\1/p' > $@
      
      $(OUT)/libproc_macro2.rlib: $(OUT)/proc_macro2.cfg $(OUT)/libunicode_ident.rlib
      	$(RUSTC) --edition=2021 --crate-type=lib --crate-name proc_macro2 $(FLAGS) \
      	    --cfg 'feature="default"' --cfg 'feature="proc-macro"' \
      	    $$(cat $<) $(call ext,unicode_ident) $(PROC_MACRO2)/src/lib.rs
      
      # ---- 3. Proc-macro crate: different crate type, must be built for the
      #         HOST (matters when cross-compiling), linked as a .so ----
      # syn's feature list must be the UNION of what serde_derive AND clap_derive
      # need. Cargo computes this (feature unification); here you work it out by hand.
      $(OUT)/libserde_derive.so: $(OUT)/libproc_macro2.rlib $(OUT)/libquote.rlib $(OUT)/libsyn.rlib
      	$(RUSTC) --edition=2015 --crate-type=proc-macro --crate-name serde_derive \
      	    $(FLAGS) --cfg 'feature="default"' \
      	    $(call ext,proc_macro2 quote syn) $(SERDE_DERIVE)/src/lib.rs
      
      # ... ~20 more rules like the above (serde, serde_json, anyhow, and
      #     clap_* each have their own quirks, and several have build.rs) ...
      
      # ---- Finally, your program ----
      # clap's derive expands to env!("CARGO_PKG_VERSION"), so you fake cargo's env.
      # Source files are listed by hand (or you wire up --emit=dep-info .d files).
      build/jsontool: src/main.rs src/cli.rs src/convert.rs $(ALL_DEPS)
      	CARGO_PKG_NAME=jsontool CARGO_PKG_VERSION=0.1.0 \
      	$(RUSTC) --edition=2021 --crate-type=bin --crate-name jsontool $(FLAGS) \
      	    $(call ext,anyhow clap serde serde_json) src/main.rs
      
      # Still to write: test (rustc --test + every --extern again), doc-tests
      # (rustdoc --test), debug vs release flags, clean, install...
      

      So are you proposing GNU style makefiles for rust then? You say not another tool, just use the compiler. Either you type those commands yourself, you type them into a make style build script, or cargo or another tool automates them.

      • Ferk@lemmy.ml
        link
        fedilink
        arrow-up
        1
        ·
        edit-2
        1 day ago

        Are you trying to compare the Makefiles? because that was not a fair comparison.

        The first one does not have the library dependencies and flags that you are wanting to use in the second one. I’d argue the first Makefile (the one for the C project) would be just as complex as the rust one if you were going to have it point to libraries under particular versioned folders set up by a third party dependency management tool (in this case, cargo… but you might as well have used nix or any other packager that uses versioned directories for its libraries).

        Earlier you said that you can use cargo as a build tool without depending on its package system… but now your proposed Makefile is doing the opposite, you are depending on cargo’s package registry (/usr/share/cargo/registry) while trying to not use cargo… which of course it’s gonna be awkward and not what was being suggested.

        • urushitan 漆たん@kakera.kintsugi.moe
          link
          fedilink
          arrow-up
          1
          ·
          edit-2
          1 day ago

          The commenter above me is proposing to build rust projects without cargo, and mentioning make files (but also saying no other build tools so idk). I’m proposing why that doesn’t scale well, and why cargo would be the best choice

          • Ferk@lemmy.ml
            link
            fedilink
            arrow-up
            1
            ·
            edit-2
            1 day ago

            I think what that person is saying is that rustc can be used in the same way as gcc (so… through makefiles, cmake and other build methods), without depending on cargo’s registry and all the crate infrastructure (your Makefile did depend on the overall cargo “crate” ecosystem). They are saying you can use existing tools with Rust, integrating Rust into the ecosystem, as opposed to making a new tool and whole new ecosystem for Rust.

            This kind of approach used with C has been working for many projects during a long time, why does it not scale well?

            • My makefile uses cargo by ripping out the cargo build information from upstream dependencies to manually run, as projects like reqwest and tokio use cargo. My demonstration was that you still would use cargo the build system rather than cargo the package manager (implying upstream registries like crates.io) over a makefile. And yes, you can use existing tools. As mentioned, systemd uses meson, though they need a python hack to get rustc working because it wasn’t working on meson upstream until 5 days ago. So yes, you can use existing tools. And as I said in another comment, for a mature project built in C, it makes sense to frankenbuild rust into the build system. But their example was purely about rust projects, I brought up makefiles to demonstrate how you can’t just slap a makefile into a rust project and it works

      • thingsiplay@lemmy.ml
        link
        fedilink
        arrow-up
        2
        ·
        2 days ago

        No, I am not talking about the same functionality and without using cargo. I am talking about using rustc to compile individual Rust programs.

        $ ls -1
        hello.rs
        main.rs
        

        main.rs:

        mod hello;
        
        fn main() {
            hello::print_hello();
        }
        

        hello.rs:

        pub fn print_hello() {
            println!("Hello World!");
        }
        
        $ rustc --target helloworld main.rs
        $ ./helloworld
        Hello World!
        

        No Cargo, no toml, no registry.

        • Ferk@lemmy.ml
          link
          fedilink
          arrow-up
          1
          ·
          1 day ago

          Nobody is gonna manually compile each .rs file one by one and link it… unless you really are specifically talking about 1 file simple projects like a helloworld, you’d want to use another tool (like make or cargo), to automate it.

          • thingsiplay@lemmy.ml
            link
            fedilink
            arrow-up
            1
            ·
            1 day ago

            What does nobody mean here? We are doing this for ages in C in example. A Makefile can help automate this process. Download git project and run make without any Cargo or regristry or other requirements. Don’t talk about everyone, just because you are not doing it.

            Also the shown example is a single rustc command that will automatically compile and link all associated (with the dependency in the files) and produce a single output.

            • Ferk@lemmy.ml
              link
              fedilink
              arrow-up
              1
              ·
              1 day ago

              If you use a Makefile, then you are not doing it manually, you are using a tool to automate it. I think that was the point.

              “nobody” means: those who have big projects with multiple .rs files but no Makefile, no cargo.toml, no build.sh, nothing, only the .rs files. Does anybody do that?

              • thingsiplay@lemmy.ml
                link
                fedilink
                arrow-up
                1
                ·
                1 day ago

                “nobody” means: those who have big projects with multiple .rs files but no Makefile, no cargo.toml, no build.sh, nothing, only the .rs files. Can you point to a project that does that?

                That’s the point. We don’t have default rustc on all machines and repositories. In example in Archlinux repositories there is not package rustc only and its standard libraries to install. That’s why most people don’t do it. You can’t fault the missing projects are missing, because the infrastructure is not there yet.

              • thingsiplay@lemmy.ml
                link
                fedilink
                arrow-up
                1
                ·
                1 day ago

                But that was not MY point at all. My point is to avoid Cargo, the registry and external dependencies from cratesio. The makefile just uses standard Linux make command. It was just an example to automate this (it could have been a simple Bash script too), but the point is it can be done manually. In this example and even in more useful ones, it can be just a simple rustc invocation.

                • Ferk@lemmy.ml
                  link
                  fedilink
                  arrow-up
                  1
                  ·
                  edit-2
                  1 day ago

                  I think one of the reasons why this is not done is because Rust deliberately works against using precompiled dependencies.

                  Unlike traditional C compilers, for the Rust compiler trying to keep ABI compatibility is a non-goal… the Rust compiler retains the freedom to aggressively optimize how data is laid out in memory and how functions are called. Binaries generated by one version (or particular settings) might not work in the next… making it much harder to work the way we work with C libraries.

                  This means managing dependencies between rust libraries is a lot more involved… since you are essentially recompiling the library along with your project, so you need a way to download, not just the library headers as you would with C, but its entire sourcecode.

                  So your approach might work if you just want to use a little bit of Rust included in a C project… or if your Rust project is small and does not have many dependencies… but the moment you start relying in more and more components and the dependency tree grows enough, it’ll become hard to manage.

                  • thingsiplay@lemmy.ml
                    link
                    fedilink
                    arrow-up
                    1
                    ·
                    1 day ago

                    I think one of the reasons why this is not done is because Rust deliberately pushes away from relying on precompiled binaries.

                    Rust? You mean the developers of Rust? That’s not a decision to make by them. Also the Linux distributions include many precompiled binaries. So I don’t see what this point is.

                    My suggestion is not to download and use the libraries from third party repositories. The idea is to have all libraries in the repository of the project itself (such as Git) or the standard library in the package manager, matching the rustc compiler that is also packaged there. In example if Debian would include such packages, then this would become quasi industry standard version everyone can target. Basically the same thing that happens with Python in example. So it stays ABI compatible.

                    I am not suggesting for full cargo replacement, and suggest not to depend on the registry of those. And I actually believe we will get to that point at some point when gccrs is ready (GNU Compiler Collection including Rust).

          • thingsiplay@lemmy.ml
            link
            fedilink
            arrow-up
            1
            ·
            2 days ago

            That’s the entire point you intelligence beast. I’m telling you the entire time “without cargo” and “include rustc and standard libraries only”. I think I told you 10 times.