Iced application panics on NixOS

Workaround for Iced calls dlopen then panics with WaylandError(Connection(NoWaylandLib)) on NixOS.

Created at
Updated at

Problem

Iced is a cross-platform Rust GUI library. An application built with it fails to launch on NixOS with an error message like this:

thread 'main' panicked at /build/cargo-vendor-dir/iced_winit-0.14.0/src/lib.rs:81:10:
Create event loop: Os(OsError { line: 89, file: "/build/cargo-vendor-dir/winit-0.30.12/src/platform_impl/linux/wayland/event_loop/mod.rs", error: WaylandError(Connection(NoWaylandLib)) })
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

winit, the underlying library for window management, tries to access libwayland via dlopen. However dlopen cannot find the library because search path is unset on Nix.

Although winit has wayland-dlopen default-on feature, iced does not have a way to disable the flag.

Solution

Use autoPatchelfHook (or patchelf) for derivation, and LD_LIBRARY_PATH environment variable for dev shell.

Derivation

In your derivation Nix file,

{
  rustPlatform,
  autoPatchelfHook,
  libgcc,
  libxkbcommon,
  vulkan-loader,
  wayland,
}
rustPlatform.buildRustPackage rec {
  # ...

  nativeBuildInputs = [
    # ... your other native build inputs ...

    # This automatically patches built binary based on "runtimeDependencies".
    autoPatchelfHook
  ];

  # autoPatchelfHook picks this attribute up and patches the binary with them.
  runtimeDependencies = [
    # These are libraries required by iced.
    libxkbcommon
    vulkan-loader
    wayland

    # This seems necessary too.
    libgcc
  ];

  # This installs these packages to the environment.
  buildInputs = [
    # ... your other build inputs ...
  ] ++ runtimeDependencies;

  # ...
}

Shell

Assuming you've already configured your derivation using the above method.

pkgs.mkShell {
  packages = [
    # ... your packages ...
  ];

  # mkShell (mkDerivation) sets an unrecognized field as an environment variable.
  LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath your-derivation.runtimeDependencies;
}

If you're using Nix Flakes, then your mkShell will look like this:

{
  # flake.nix
  # ...

  outputs = { nixpkgs, ... }:
    let
      # Simplified
      system = "...";
      pkgs = nixpkgs.legacyPackages.${system};
    in rec {
      packages.${system}.default = pkgs.callPackage ./path/to/derivation.nix { };

      devShells.${system}.default = pkgs.mkShell {
        packages = [
          # ... your packages ...
        ];

        LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath packages.${system}.default.runtimeDependencies;
      };
    };
}