• 作成日時:
  • 更新日時:

Gentoo上のEmacsでまともなRust環境を構築しました,バグ報告で問題が解決しました

Rustその2 Advent Calendar 2018 - Qiitaの10日目の記事です.

バグ報告した影響か,rustic-modeの作者の修正によって以下の内容はあっという間に陳腐化しました.

最後の章で修正を記述します.なので最後の章を読んで内容を実行してください.陳腐化した内容はこれはバグとの戦いとして一応内容を残しておくことにします.

  • これまでrust-modeとflycheck-rustだけでEmacsでRustを書いていましたがそろそろ限界になってきました
    • flycheck-rustが安定してなくてエラーが出るときと出ない時があったためです
  • racerを導入
  • rustic-modeを導入するも,めちゃくちゃバグ報告した,workaroundも書きました
できた環境
できた環境

Gentooだと何が問題なのか

Firefoxが既にRustを使っていることは有名です.また私はRust製の高速grepであるripgrepを使っています.

Shift_JISに対応しているのでag(the_silver_searcher)からrg(ripgrep)に乗り換えました - ncaq

それでGentooはソフトウェアを原則全てビルドするので,これらのビルドには当然Rustが必要です.よってビルド時にRustをシステムにインストールします.

するとrustupが「もうシステムにrustc入ってるからインストール出来ないんですけど!」って怒ってインストールできなくなります.

これまで「じゃあシステムのRust使うか,nightlyは私は使わないし」ってやってたんですが,色々なツールチェーンがrustupを使うことを前提にインストールするので,rustupをインストールしないと何もかも無理なことがわかりました.

幸い,Gentooの依存は「ビルド時の依存」と「実行時の依存」に分かれているので,Firefoxなどをビルドし終えたらシステムのRustはアンインストールして構いません.

ここでemerge -cするだけでは不十分で,virtual/rust, virtual/cargoは明示的に

emerge -C virtual/cargo virtual/rust

してからemerge -cする必要があったので注意です.

これからFirefoxのアップデートするたびにRustのインストールが走るんですが,rust-binの方を優先的にインストールするようには設定できないのかな…rustの方をマスクしてしまえばrust-binの方に誘導されるかな?

package.mask

dev-lang/rust

と書いてしまえばFirefoxのビルド時にもrust-binの方に誘導されるようですね.

これをやっていればemerge -uDN worldした後にemerge -cすればOKになるはずです.なりませんでした.毎回emerge -C virtual/cargo virtual/rustが必要です.どうしてそうなるんでしょう…

.zprofileの謎

rustupは.profile.zprofileの両方にパスを追加しますが,何故か私のzshは.profileは読み込むのですが.zprofileは読み込みませんでした.仕方がないので.profilePATHを記述しています.

rustupとcargoのzsh向け補完

プログラムが補完ファイルを吐き出すので,

mkdir -p /tmp/$USER-zsh-completions/
rustup completions zsh > /tmp/$USER-zsh-completions/_rustup

fpath=(
    /tmp/$USER-zsh-completions/
    $(rustc --print sysroot)/share/zsh/site-functions
    $ZDOTDIR/lib/zsh-completions/src
    $ZDOTDIR/lib/gentoo-zsh-completions/src
    $fpath)

のようにする必要があります.

racer

racer-rust/racer: Rust Code Completion utility

オムニ補完してくれるやつです.Haskellばかり使ってたのでオムニ補完のこと考えてませんでしたが,Rust使う場合はやっぱりあった方が便利ですね.

コード補完だけじゃなくてコードジャンプや簡易ドキュメント閲覧も出来るようですね.

公式ドキュメント通りにインストールしたら

racer complete std::io::B

で動作を確認できます.

racerのオムニ補完って標準ライブラリのデータ構造には効きますけど,自前のデータ構造には効かないんですね.これで動いてないのかと勘違いしてしまいました.標準ライブラリに対しては定義ジャンプもちゃんと動いて満足です.

rustic-mode

brotzeit/rustic: Rust development environment for Emacs

Emacs で Rustの開発環境を構築する話 – mopemope – Medium

に書かれているように,rust-modeをforkしたrustic-modeというのがあって,rustfmtやclippyに対するサポートが手厚いようです.

これまで使っていたrust-modeやflycheck-rustを無効化して,これをインストールしてみました.

checkがEmacsから実行できるのは良いですね.

flycheckで良いじゃんと思ってましたが,flycheckはエラーメッセージをコンパクトにしてしまうので,rustのAAを駆使したフルのエラーメッセージを見たい時は結構あります.

rust-modeで開かれる時がある

rustic-modeではなくrust-modeで開かれることがあります.racerが依存しているのでrust-modeを取り除くことは出来ません.

workaroundを書いて,

;; rust-modeで開かれる時があるのでrustic-modeを末尾に追加し直す
(cl-delete-if (lambda (element) (equal (cdr element) 'rust-mode)) auto-mode-alist)
(cl-delete-if (lambda (element) (equal (cdr element) 'rustic-mode)) auto-mode-alist)
(add-to-list 'auto-mode-alist '("\\.rs$" . rustic-mode))

issueを建てました.

rust-mode overrides auto-mode-alist rather than rustic-mode · Issue #20 · brotzeit/rustic

lsp-modeのインストールプロンプトが毎回出る

しかし,lsp-modeをインストールしているのに毎回

lsp-mode not found. Install it ? (y or n) y

というプロンプトが出ます.

当然

‘lsp-mode’ is already installed

となるわけですが…

autoloadされない感じか?と思って,.el

(require 'lsp-mode)

と書いてみました.

そうしたらプロンプトは避けられました.

この件についてissueを建てました.

If there is no (require 'lsp-mode), the install dialog will appear each time · Issue #21 · brotzeit/rustic

存在しない関数であるlsp-rust-enableを呼んでいる

rustic-modeでファイルを開くと,

File mode specification error: (void-function lsp-rust-enable)

というエラーが出ます.

どうやら明示的な依存関係には書かれていませんがlsp-rustパッケージが必要なようですね.

明示的に書かれていないのはlsp-modeだけじゃなくてeglotなどが選択できるからですかね?私はどっちが良いのか全くわからないのでデフォルトのlsp-modeを使っています.

と思ってlsp-rustを入れてベタ書きrequireしてみたのですが

File mode specification error: (void-function lsp-rust-enable)

が変わらず出て,このエラーが出るせいで他のhookに登録している関数が動かないので,racerを自動的に有効にすることもできません.

とりあえずワークアラウンドを書いておきました.

;; lsp-rust-enableが消滅したのでバッドノウハウとしてダミーの関数を定義する
(unless (fboundp 'lsp-rust-enable)
  (defun lsp-rust-enable ()
    ()))

issueも立てました.

File mode specification error: (void-function lsp-rust-enable) · Issue #23 · brotzeit/rustic

lsp-modeのバージョンアップで関数が消滅したらしいですね.

clippyが動かない

Suspicious state from syntax checker rustic-clippy: Flycheck checker rustic-clippy returned non-zero exit code 1, but its output contained no errors: error: 'cargo-clippy' is not installed for the toolchain 'nightly-x86_64-unknown-linux-gnu'
To install, run `rustup component add clippy`

というエラーがでてclippyが動きません.

rustupは

% rustup component add clippy
info: component 'clippy' for target 'x86_64-unknown-linux-gnu' is up to date

と既にインストールしていると返してきます.

nightlyをターゲットにしてインストールしないといけないのかなと思って

% rustup toolchain add nightly
info: syncing channel updates for 'nightly-x86_64-unknown-linux-gnu'
info: latest update on 2018-12-15, rust version 1.33.0-nightly (96d1334e5 2018-12-14)
info: downloading component 'rustc'
 80.0 MiB /  80.0 MiB (100 %)  17.3 MiB/s ETA:   0 s
info: downloading component 'rust-std'
 55.5 MiB /  55.5 MiB (100 %)  19.9 MiB/s ETA:   0 s
info: downloading component 'cargo'
info: downloading component 'rust-docs'
info: installing component 'rustc'
info: installing component 'rust-std'
info: installing component 'cargo'
info: installing component 'rust-docs'

  nightly-x86_64-unknown-linux-gnu installed - rustc 1.33.0-nightly (96d1334e5 2018-12-14)
% rustup component add --toolchain nightly clippy
info: downloading component 'clippy'
info: installing component 'clippy'

してみました.

そしたらちゃんとclippy含めたflycheckが動作するようになりました.一度rustic-popupからcheckかけるようにしないとflycheckは動作し始めないようですが.

多分これ開発者nightlyしか使ってない奴ですね?

これはエラーメッセージを変更するpull request投げた方が良さそうですね.と思ってforkしてみたのですが何処でエラーメッセージを出しているのか全くわからないので,何処を直せば良いのかわかりません.

とりあえずissueだけ建てておきました.

flycheck rustic-clippy clippy installation guide is wrong · Issue #18 · brotzeit/rustic

rustfmtのエラーが出るとバッファが立ち上がってフォーカスが移ってうざい

rusticはデフォルトでrustfmtを保存時に実行するようになっています.

それは良いのですが,rustfmtが解釈できないような構文エラーのプログラムを書いた時にバッファが出てきます.

それも良いのですが,何故か開いたバッファにフォーカスを移動するので滅茶苦茶イラッとします.

抑制する設定を加えるpull request作ろうかなと思ってソース見たら既にその設定がありました.

(defcustom rustic-format-display-method 'pop-to-buffer
  "Default function used for displaying rustfmt buffer."
  :type 'function
  :group 'rustic)

しかもバッファがflycheckのものでは無いので,Emacsのウィンドウを縦2分割して左側にプログラムを表示して右側にflycheckを表示している私のスタイルだと,ウィンドウが破壊される…と思ったのですが,pop-to-bufferは役割を終えたら自動で閉じるのでその心配は無さそうですね.

これの対処ですが少し難しくて,この関数が呼び出された時点ではcurrent windowがrustfmt procのものに変更されているため,1つ前のウィンドウに戻すとかいうクソ雑実装するしかありませんでした.

;; 本当にwithout switchしているわけではなく前のウィンドウにフォーカスを戻すだけ
(defun pop-to-buffer-without-switch (buffer-or-name &optional action norecord)
  (pop-to-buffer buffer-or-name action norecord)
  (other-window -1)
  )

(custom-set-variables '(rustic-format-display-method 'pop-to-buffer-without-switch))

とか書いてたんですが,これ新しいrustic-modeの修正でおそらく必要なくなりました.必要になったら呼び戻します.

rustic-modeのrustfmtを実行するとマルチバイト文字が全て文字化けするけど作者によって超速で直された

編集して保存すると日本語コメントが全て文字化けするという恐怖体験がありました.

文字化けするというissueを建てたら,

Saving multibyte characters garbled · Issue #19 · brotzeit/rustic

即座に作者によって修正されました.今はmelpaバージョンにも反映されていると思います.

現在のRust関連設定ファイル

作者の即座の修正と数々のworkaroundによって平穏がもたらされました.

flycheckの表示が不安定だった問題が解決されたことによる心の平穏は大きいです.

現在の90_rust.elは以下のようになっています.

;; -*- lexical-binding: t -*-

(require 'lsp-mode)

;; rust-modeで開かれる時があるのでrustic-modeを末尾に追加し直す
(cl-delete-if (lambda (element) (equal (cdr element) 'rust-mode)) auto-mode-alist)
(cl-delete-if (lambda (element) (equal (cdr element) 'rustic-mode)) auto-mode-alist)
(add-to-list 'auto-mode-alist '("\\.rs$" . rustic-mode))

(add-hook 'rustic-mode-hook 'racer-mode)
(add-hook 'racer-mode-hook 'eldoc-mode)

;; lsp-rust-enableが消滅したのでバッドノウハウとしてダミーの関数を定義する
(unless (fboundp 'lsp-rust-enable)
  (defun lsp-rust-enable ()
    ()))

(with-eval-after-load 'racer
  (define-key racer-mode-map (kbd "C-c C-d") 'rustic-racer-describe))

rustic-modeの作者の修正によってこれまでの内容が陳腐化しました

主な内容はlsp-modeではなくeglotを使うようになったことです.

これにより

  • racerが直接必要なくなった(これ元からかも?)
  • lsp-modeが必要なくなった
  • rlsを必要とするようになった
  • ポップアップフォーカス抑制コードが再び必要になった

などの影響が起こりました.

eglotだとlsp-modeの数々の問題は起きません.

rlsでracerを直接使わずにコード補完や内容の修正提案の適用ができます.

% rustup component add rls-preview rust-analysis rust-src

でrlsをインストールして,バックエンドを使えるようにしておきましょう.

インストールダイアログが毎回出てしまう問題はeglotでは起きません.

それでeglot向けのEmacs Lispを書きましょう.

;; -*- lexical-binding: t -*-

(with-eval-after-load 'eglot
  (define-key eglot-mode-map (kbd "C-c C-d") 'eglot-help-at-point)
  (define-key eglot-mode-map (kbd "C-c C-r") 'eglot-code-actions)
  )

eglot-help-at-pointはポイントの下の関数のドキュメントコメントを表示するやつです.racerの出すやつの方が分量が多いのでそっちが好みの人はracerを残しておいて使っても良いかもしれません.

eglot-code-actionsは便利な関数で,Haskellのinteroがやってるように,「ここmutいらないよ」のような修正をエディタが自動的に行ってくれます.haskell-ide-engineも成熟してinteroと似たようなこと出来るようになったみたいですしそろそろ移行しても良いかも…eglotから使えますし.

eglot-renameは個人的にはmulitple-cursorで十分なので要らないかな.

それで現在のrustic-mode向けEmacs Lispはeglot向けのと合わせて以下です.

;; -*- lexical-binding: t -*-

;; rust-modeで開かれる時があるのでrustic-modeを末尾に追加し直す
(cl-delete-if (lambda (element) (equal (cdr element) 'rust-mode)) auto-mode-alist)
(cl-delete-if (lambda (element) (equal (cdr element) 'rustic-mode)) auto-mode-alist)
(add-to-list 'auto-mode-alist '("\\.rs$" . rustic-mode))

;; 本当にwithout switchしているわけではなく前のウィンドウにフォーカスを戻すだけ
(defun pop-to-buffer-without-switch (buffer-or-name &optional action norecord)
  (pop-to-buffer buffer-or-name action norecord)
  (other-window -1)
  )

;; エラーポップアップにフォーカスを移さない
(custom-set-variables '(rustic-format-display-method 'pop-to-buffer-without-switch))