本記事は、 Rust Advent Calendar 2024 4日目 の記事だ。
ついでに、 Windows Advent Calendar 2024 4日目 の記事としても登録させてもらっている。
どちらもまだ空きが在るので是非登録してね!
Rust Advent Calendar 2024 シリーズ 2 の 3日目 の記事は、 @yo-naka 氏の Observable Framework のデータローダーを Rust で書く方法 #ObservableFramework 。
データ可視化用の静的サイトジェネレーター Observable Framework のデータローダーを、省メモリで高速に処理できる Rust で書くという内容だった。
さて4日目の本記事では、プロキシ環境下にて Windows + vscode + Rust Analyzer の組み合わせで使用しようとしたところ、標準ライブラリ等のコード補完が期待通り動いてくれなかった問題の対処メモを共有したい。
ついでに、プロキシ環境下のセットアップや、 vscode でデバッグする手順も示す。
再現手順
とりあえず、まずプロキシ環境下でセットアップしてみよう。
基本的に Windows - The rustup book の手順をベースにしている。
途中、 PowerShell の http_proxy
, https_proxy
変数や JSON にての設定などで記述しているプロキシのアドレスは、利用する環境にあわせて書き換えてくれ。
1) Build Tools for Visual Studio をインストール
Visual Studio の何らかのライセンスの対象の場合 は、 Build Tools for Visual Studio をインストールする。
→ Visual Studio Installer
最低限、個別のコンポーネントで MSVC v14* - VS 20** C++ x64/x86 ビルド ツール (最新)
と、 Windows 1* SDK
をインストールしよう。
もし、 Visual Studio (非 vscode) の重量級 IDE を使うつもりがあるなら、 『C++ によるデスクトップ開発』 のワークロードをインストールすれば、上記コンポーネントも同時にイントールされる。
公式の手順では Visual Studio 2022 (MSVC v143) をインストールしているが、サポート期間が残っている Visual Studio 2017 (MSVC v141) などでも良いようだ。 [^1] [^2]
Visual Studio のライセンスについて、少し補足をば。
プロキシの環境下にあるようなユーザーは、おそらく何らかの組織に所属しているような人だろう。
ビルドツールのライセンス条項 によると、 OSS の依存関係の中でビルドする場合を除けば、 Build Tools for Visual Studio の利用には Community, Professional, Enterprise のいずれかの Visual Studio ライセンスが必要だ。
個人開発者なら Community のライセンスが使えるものの、 250人 or 250台のPC or 収益 $100万 のいずれかを超える会社に所属したり、その下請けとして利用する場合など、「エンタープライズ利用」と分類される場合は、基本的に Professional, Enterprise が必要となる。
もし、 Visual Studio のライセンスが使えない場合、この手順はスキップし (1.5) に進んで MinGW64 をインストールし、更に (2.5) の手順で rust の toolchain を stable-gnu
に設定しよう。(後述)
1.5) VC++ 再配布可能パッケージと MinGW64 のツールチェーンをインストール
(1) で Build Tools for Visual Studio をインストールしなかった人向け。
Build Tools for Visual Studio インストール済みの方はこの項を実行せず (2) にスキップ。
Build Tools for Visual Studio (MSVC) が使えない場合は、替わりに GNU の ABI を使う環境を整えよう。
PowerShell を立ち上げ、 winget を使って Visual C++ 再頒布可能パッケージ と Msys2 をインストールし、 Msys2 の MinGW64 環境を立ち上げる。
$env:http_proxy = $env:https_proxy = 'http://proxy.example.com:8080';
winget install --id Microsoft.VCRedist.2015+.x64;
winget install --id=MSYS2.MSYS2;
C:\msys64\mingw64.exe;
Msys2 の MINGW64 環境のシェルが立ち上がったら、 pacman で gcc のビルドチェーンをインストールする。
pacman -Sy mingw-w64-x86_64-toolchain
インストールが完了したら、 Windows 側の環境変数の設定で、 C:\msys64\mingw64\bin
の PATH を通しておこう。
なお、 Msys2 の MINGW64 をインストールしなくても Rust のコンパイル自体は行える。
ただし、多くのライブラリ Rust クレートは MINGW64 が無いとビルドできないため、基本的に MINGW64 をいれておこう。 [^3]
ちなみに VC++ 再頒布可能パッケージの方は、これを入れておかないと、後ほど rust-analyzer を起動した際に言語サーバーが以下のようなエラーを吐く。
[Error - 11:01:56 PM] Server initialization failed.
Message: Pending response rejected since connection got disposed
Code: -32097
[Info - 11:01:56 PM] Connection to server got closed. Server will restart.
true
[Error - 11:01:56 PM] Rust Analyzer Language Server client: couldn't create connection to server.
Message: Pending response rejected since connection got disposed
Code: -32097
[Error - 11:01:56 PM] Server process exited with code 3221225781.
rust-analyzer の言語サーバーは Rust 書かれていて MSVC でビルドされているようなので、 VC++ 再頒布可能パッケージが必要なのは当然っちゃ当然なんだが、ちゃんとドキュメントに必須コンポーネントと書いておいて欲しい。
どうせ Build Tools for VS 入れるから、言わんでも入るだろということなのだろうか…?
2) rustup で Rust をインストール
Rust のインストールには Rustup を使おう。
PowerShell を起動し、以下を実行する。
$env:http_proxy = $env:https_proxy = 'http://proxy.example.com:8080';
winget install --id Rustlang.Rustup;
自動的に staube-msvc
のツールチェーンや、主要なコンポーネントがインストールされる
2.5) ツールチェーンを stable-gnu に切り替える
(1) で Build Tools for Visual Studio をインストールしなかった人向け。
Build Tools for Visual Studio インストール済みの方はこの項を実行せず (3) にスキップ。
改めて PowerShell を起動し直し、以下を実行する。
# Visual Studio をインストールしなかった人のみ
$env:http_proxy = $env:https_proxy = 'http://proxy.example.com:8080';
rustup toolchain install stable-gnu;
rustup default stable-gnu;
実は...
winget のバージョンが 1.5 以上 なら、手順 (2) の処理を以下のように変更することで、最初から stable-x86_64-pc-windows-gnu のツールチェーンがインストールされる。
$env:http_proxy = $env:https_proxy = 'http://proxy.example.com:8080';
winget install --id Rustlang.Rustup --custom '--default-toolchain=stable-gnu';
そうすれば、 (2.5) の手順が不要になる。
3) vscode のセットアップ
vscode のインストールと、プロキシ回りの設定をしておく。
- Microsoft の Visual Studio Code のサイト から vscode をインストール
- vscode 上部のコマンドパレットで
> Open User Settings (JSON)
と入力してsettings.json
を開き、以下の JSON 構造を追記。{ "http.proxy": "http://proxy.example.com:8080", "terminal.integrated.profiles.windows": { "PowerShell": { "source": "PowerShell", "icon": "terminal-powershell", "env": { "https_proxy": "http://proxy.example.com:8080", }, }, }, }
- 既に何らかの設定が入っていたら、 JSON 構造的にうまくマージしてくれ。
- vscode を立ち上げ直し、任意の(空の)フォルダを開く
- vscode の拡張機能で rust-analyzer をインストール
コード補完が効かない問題の発生
さて、コレで環境が整ったはず。
- vscode のターミナル上で
cargo init
などと入力し、任意の Rust プロジェクトを作成する - 適当に
main.rs
のソースを書いてみる。#[derive(Debug)] struct Location(i32, i32); fn main() { println!("Hello, world!"); let loc = Location(42, 42); let mut tvec = Vec::new(); tvec.push(1); tvec.push(2); println!("{:?} {:?}", loc, tvec); }
- ターミナルでビルドと実行してみる。
cargo build
cargo run
- ちゃんと動いていそう。
Hello, world! Location(42, 42) [1, 2]
- ちゃんと動いていそう。
- ところが、コード内で定義した型 (
Location
構造体) の型推論は動いているのに、 標準ライブラリ (Vec::new
) を使った方は型推論もされず{unknown}
となることに加えコード補完も動かない。
うーん…
原因
エラーメッセージ上は、 {sysroot_path}
の設定 (vscode の設定上の rust-analyzer.cargo.sysroot
など) に rustc --print sysroot
で出力した内容を記述すればうまく動きそうだが、それは上手く行かない。
そもそもの原因は単純で、 rust-analyzer がプロキシの設定を読めていない為だ。
vscode は以下の 3箇所 でプロキシの設定がある。
- vscode 本体 (拡張機能のインストールなど) は、 OS のプロキシ設定を読む
- vscode の拡張機能などは(本来)、 vscode の設定の
http.proxy
を読む - vscode のターミナルは、 vscode の設定の
terminal.integrated.profiles.windows.*.env
を読む
本来 rust-analyzer は (2) のプロキシ設定を解釈すべきなのだが、どうも無視してしまうようだ。
厳密には、 非HTTP 通信には適用されているものの、 HTTPS 通信には適用されないため、 HTTPS で配信されているコンポーネントやクレートが落とせないのが問題なようだ。
対策
vscode プロセスそのものの環境変数に https_proxy
を設定すれば、解消する。
方法がざっくり2種類ある。
対策法1: OS の環境変数
Windows 側のシステムまたはユーザーの環境変数に、上記値を設定する方法。
多分これが一番手っ取り早いのでオススメ。
但しすべてのアプリに影響してしまうので、場合によっては他のイントラネットアプリが動かなくなってしまうこともあるだろう。
対策法2: Rust Analyzer 設定の環境変数
vscode 設定の rust-analyzer.server.extraEnv
に、 https_proxy
の環境変数を設定する方法。
vscode 上部のコマンドパレットで > Open User Settings (JSON)
と入力して settings.json
を開き、 "rust-analyzer.server.extraEnv"
の項目を追加して、 設定したい環境変数を設定する。
再現手順 (3) で設定した JSON に追記すると以下のようになる。
{
"http.proxy": "http://proxy.example.com:8080",
"terminal.integrated.profiles.windows": {
"PowerShell": {
"source": "PowerShell",
"icon": "terminal-powershell",
"env": {
"https_proxy": "http://proxy.example.com:8080",
},
},
},
"rust-analyzer.server.extraEnv": {
"https_proxy": "http://proxy.example.com:8080",
}
}
環境変数の影響が言語サーバーに閉じるので、おそらく一番バランスが良い対策方法だろう。
なお、 Rust Analyzer で環境変数をピンポイントで定義できる設定は、他にも
rust-analyzer.runnables.extraEnv
rust-analyzer.cargo.extraEnv
rust-analyzer.check.extraEnv
があるが、 rust-analyzer.server.extraEnv
なら cargo 実行時にも適用されるようだ。
適用対象: | ビルドしたコード実行時 | cargo や rustc 実行時 |
|
cargo check 実行時 |
|||
.check.extraEnv |
× | × | ○ |
.cargo.extraEnv |
× | ○ | ○ |
.runnables.extraEnv |
○ | × | × |
.server.extraEnv |
× | ○ | ○ |
デバッグ実行時に環境変数が適用されて欲しくない! 等があれば、適切な範囲となる設定に調整するのが良いだろう。
コードでの環境変数一覧の取り方
Rust の標準ライブラリで環境変数一覧にするのは、以下のように非常に簡単にできる。
fn main() {
for (key, value) in std::env::vars() {
println!("{}={}", key, value);
}
}
これを Win32 API から取ろうとすると、 GetEnvironmentStringsW 関数 が '\0'
で区切られた環境変数文字列リストで、最後が '\0'
2つとなっているメモリの先頭アドレスを返すという、 Win32 おなじみの凶悪な仕様なので、 windows クレート使っても以下のようなしんどいコードになっちゃう。
use windows::core::*;
fn main() -> Result<()> {
unsafe {
let pcw_blocks = windows::Win32::System::Environment::GetEnvironmentStringsW();
if pcw_blocks.as_ptr().is_null() {
return Err(Error::from_win32());
}
let env_block_ptr = pcw_blocks.as_ptr();
let mut len = 0;
let mut current_ptr = env_block_ptr;
while *current_ptr != 0 || *(current_ptr.add(1)) != 0 {
if *current_ptr != 0 { len += 1; }
current_ptr = current_ptr.add(1);
}
let slice = std::slice::from_raw_parts(env_block_ptr, len + 1);
let mut start = 0;
for (i, &c) in slice.iter().enumerate() {
if c == 0 {
if start != i {
let utf16_slice = &slice[start..i];
println!("{}", HSTRING::from_wide(utf16_slice)?.to_string_lossy());
}
start = i + 1;
}
}
windows::Win32::System::Environment::FreeEnvironmentStringsW(pcw_blocks)?;
}
Ok(())
}
対策法3: プロセスの環境変数
一回だけ vscode を立ち上げる際に、プロセス環境を設定する方法。
具体的には、 PowerShell 上で一時的な環境変数を設定し、カレントディレクトリを移動したあと、 code .
で vscode を立ち上げる。
$env:https_proxy = 'http://proxy.example.com:8080';
cd 'path\to\project_dir';
code .;
一度上記環境で vscode の rust-analyze が起動すれば、標準ライブラリのコード補完に必要なコンポーネントやクレートはダウンロードされる。
このため、次回起動時に通常通り vscode 立ち上げても、引き続きコード補完は期待通り動く。
但し、 Cargo.toml を書き換えて依存クレートが変更されると、変更後の依存関係の情報の更新に失敗するので、そのときはまた環境変数を設定した vscode に立ち上げ直す必要がある。
対策後
上記いずれかの対策を行って、 rust-analyze 内部の Cargo のモジュール解決が済めば、以下のように正しく型推論やコード補完が実行されるようになるはずだ。
おまけ: vscode でデバッグする設定
vscode でデバッグする場合も、 Build Tools for VS を入れたか否かで変わってくる。
それぞれ対応する拡張機能をいれさえすれば launch.json
が無くとも、 rust-analyzer によって main
関数の上に描画された Debug
ボタンをクリックすればデバッグできる。
launch.json
記述方法について詳しくは以下を参照。
Build Tools for VS + ツールチェーン x86_64-pc-windows-msvc
の場合
C/C++ 拡張機能を入れる。
launch.json
書く場合は以下のようにする。
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch App Debug",
"type": "cppvsdbg",
"request": "launch",
// change the exe name to your actual exe name
// (to debug release builds, change `target/debug` to `release/debug`)
"program": "${workspaceRoot}/target/debug/${workspaceFolderBasename}.exe",
"cwd": "${workspaceRoot}",
"preLaunchTask": "rust: cargo build"
}
]
}
ツールチェーン x86_64-pc-windows-gnu
の場合
CodeLLDB 拡張機能を入れる。
launch.json
書く場合は以下のようにする。
{
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug executable bin",
"cargo": {
"args": [
"build",
],
"filter": {
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}",
"preLaunchTask": "rust: cargo build"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in executable bin",
"cargo": {
"args": [
"test",
"--no-run",
],
"filter": {
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}",
"preLaunchTask": "rust: cargo build"
}
]
}