Skip to content

Cannot start rust-analyzer in Cargo workspace not in project root #110

@mgeisler

Description

@mgeisler

Hello! Thanks so much for maintaining rustic-mode! I'm not 100% sure if this fix should be in rust-mode or rustic-mode, but I wanted to file it and discuss. Happy to move or re-file it later 😃

In short, I believe one of the Rust modes should configure how Emacs finds the current project because Eglot uses this to start rust-analyzer.

When rust-analyzer is started, it fails to discover crates if the Cargo workspace is nested more than one directly level inside a Git repository. As far as I can tell, this is because of find_cargo_toml_in_child_dir, which says:

        fn find_cargo_toml_in_child_dir(entities: ReadDir) -> Vec<ManifestPath> {
            // Only one level down to avoid cycles the easy way and stop a runaway scan with large projects

The problem occurs when the Emacs and Cargo don't agree on where the project root is. Eglot relies entirely on project.el to determine the project root (see joaotavora/eglot#909). The default backend for project.el is project-vc-find-project, which simply finds the version control root (.git directory). When rust-analyzer is started with the VCS root, it cannot find the workspace members and reports an error:

[eglot] Server reports (type=1): Failed to discover workspace.
Consider adding the `Cargo.toml` of the workspace to the [`linkedProjects`](https://rust-analyzer.github.io/book/configuration.html#linkedProjects) setting.

Failed to load workspaces.

I'll suggest that rustic-mode fixes this by explicitly configuring how to find the Cargo root. I've been using the following configuration snippet for a few days now and it seems to do the job:

;;; --- Configure project.el to find Cargo project roots intelligently ---

(require 'cl-lib)

(defun mg/cargo-toml-is-workspace (dir)
  "Return non-nil if DIR contains a workspace Cargo.toml file."
  (let ((cargo-toml (file-name-concat dir "Cargo.toml")))
    (when (file-exists-p cargo-toml)
      (with-temp-buffer
        (insert-file-contents-literally cargo-toml)
        (goto-char (point-min))
        (search-forward-regexp "^\\[workspace\\]" nil t)))))

(defun mg/project-find-rust-project (dir)
  "Find a Rust project root, prioritizing workspaces."
  (let ((ws-root (locate-dominating-file dir #'mg/cargo-toml-is-workspace)))
    (cond
     ;; Priority 1: A Cargo workspace was found. Use it directly.
     (ws-root (cons 'cargo-project ws-root))

     ;; Priority 2: No workspace, so find the nearest `Cargo.toml`.
     ((when-let ((cargo-dir (locate-dominating-file dir "Cargo.toml")))
        (let* ((vcs-root (vc-root-dir))
               (project-root
                (if-let ((vcs-root-path (and vcs-root (file-name-as-directory vcs-root))))
                    ;; A VCS root exists. Decide based on nesting depth.
                    (let* ((cargo-path (file-name-as-directory cargo-dir))
                           (relative-path (file-relative-name cargo-path vcs-root-path))
                           (depth (cl-count ?\/ relative-path)))
                      (if (> depth 1)
                          ;; Deeper than 1 level: use the Cargo.toml dir.
                          cargo-dir
                        ;; 0 or 1 level deep: use the VCS root.
                        vcs-root))
                  ;; No VCS root found, so the Cargo.toml dir is the best we can do.
                  cargo-dir)))
          (cons 'cargo-project project-root)))))))

;; Teach project.el how to extract the root from our 'cargo-project type.
(cl-defmethod project-root ((project (head cargo-project)))
  (cdr project))

;; Add our new function to the front of `project-find-functions` to give it
;; priority over the default VCS-based discovery.
(add-to-list 'project-find-functions #'mg/project-find-rust-project)

The logic for finding the correct Cargo.toml file could probably be improved: calling cargo metadata and parsing out the workspace_root is probably the right solution.

The steps I used to test this locally was:

mkdir -p project-root
cd project-root
git init

mkdir -p nested
cd nested

cargo new --lib foo

To test, open project-root/nested/foo/src/lib.rs in Emacs and start Eglot.

Version Info:

  • Rustic Version: 20250630.1332
  • Emacs Version: 30.1 from Debian Testing
  • Eglot Version: 1.17.30
  • rust-analyzer Version: 1.89.0 (2948388 2025-08-04)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions