はしくれエンジニアもどきのメモ

情報系技術・哲学・デザインなどの勉強メモ・備忘録です。

Docker+VSCodeのRust環境を作る

Docker+VSCodeのRust環境を作る

RustがCとPythonの間(C寄り)の中級型言語でCより書きやすく速度はCと大差ないらしいということで、触りたくなったのでWindowsでの環境構築を調べたメモ。

GUIアプリを作るようなケースでなければ以下の方法で問題ないはず。

  • .devcontainerを使わない方法(1ファイルのみのコンパイル・実行なら可能)
  • .devcontainerを使う方法(ワークスペース・パッケージ単位のビルド、1パッケージのデバッグ、補完やらが可能。基本的にはこっち)

環境・事前準備

VSCode(+拡張機能RemoteContainer)とDockerDesktopはインストール済みを想定

Rustはどんな言語か

https://www.rust-lang.org/ja

インストールした環境では以下のコマンドが使えるようになる。

  • rustc:コンパイル
  • cargo: NodeJSのnpmに近い
  • rustup: rustのバージョン管理、クロスコンパイルのターゲット先指定

.devcontainerを使わない場合

これは公式のRustイメージでコンパイルする方法である。

まず、公式のRustイメージをインストールしておく。

docker pull rust:latest

VSCodeで以下のHello Worldのコードを書く。 なお直接Windows上にRustをインストールしてないのでマクロの補完は効かない。

// hello.rs

fn main(){
    println!("hello world!")
}

ディレクトリ構造は

- Project
  - hello.rs

こいつをVSCode上のターミナルからrustcコマンドでコンパイルする。

PS Project> docker run -it --rm -v "$(PWD):/usr/src/myapp" rust /bin/sh -c "rustc /
usr/src/myapp/hello.rs; ./hello"
hello world!

f:id:cartman0:20220214220238p:plain
Rustコンテナ立ち上げと同時にコンパイル

.devcontainerを使う場合

  • 公式のRustイメージをインストールする必要なし(間接的にレイヤとしてインストールされるが)
  • デバッグ可能(lldbデバッガ)

参考: VS Code + Remote Containers で快適な Dev ライフを | サン・エム・システム株式会社

  1. プロジェクト(ワークスペース)にするフォルダを右クリックしてVSCodeを開く
  2. 左下「Open a Remote Window」マークをクリック
    f:id:cartman0:20220214220251p:plain
    Open a Remote Window
  3. 「Reopen in Container」を選択。
    f:id:cartman0:20220214220235j:plain
    Reopen in Container
  4. ここで対応している言語が選べる。今回は「Rust」を選ぶ。
    f:id:cartman0:20220214220241p:plain
    言語選択
  5. ContainerのOSを選択。「buster」にする。
    f:id:cartman0:20220214220244p:plain
    ContainerのOS選択
  6. 追加インストールするものを決める。今回はなし
    f:id:cartman0:20220214220254p:plain
    オプションのインストール

以上全てを設定するとDLが始まる。

この手順で導入するとVSCode用Rustイメージ(公式のRustイメージがベースイメージ)がインストールされる。 (初期インストールは時間がかかる)

また、VSCode拡張機能がnpmのローカルインストールと同じ感じでインストールされる。

devcontainerを閉じた後にもう1度開くには, 左下マークをクリックして「Open Folder in Container」を選択してプロジェクト用のフォルダを開いてあげればいい。

出来上がるファイル構成

インストールが終わると次のファイル構成になる。 VSCode側が生成したものとRust側が生成したものがあるので注意。

- Project
  - .git
  - .gitignore
  - .devcontainer
    - Dockerfile // このイメージがdocker pullされる
  - .vscode
    - launch.json // デバッガ用の構成ファイル
// 👆ここまでがVSCode側で生成
// 👇ここからcargo initで生成されたファイル
  - src
    - main.rs // hello worldが書かれている
- Cargo.toml // Rustのパッケージ情報など
- Cargo.lock // Rustのworkspace内での依存関係の明示など

動作確認

Rust関連のコマンドが使えるか確認

vscode ➜ /workspaces/rust_project (main ✗) $ cargo -V
cargo 1.58.0 (f01b232bc 2022-01-19)
vscode ➜ /workspaces/rust_project (main ✗) $ rustc -V
rustc 1.58.1 (db9d1b20b 2022-01-20)
vscode ➜ /workspaces/rust_project (main) $ rustup --version
rustup 1.24.3 (ce5817a94 2021-05-31)
info: This is the version for the rustup toolchain manager, not the rustc compiler.
info: The currently active `rustc` version is `rustc 1.58.1 (db9d1b20b 2022-01-20)`

Rust workspaceの設定

上記の設定ではRustの1パッケージのファイル構成なので、 次のようなパッケージが2つあるようなworkspace用の構成に作り変える。

https://github.com/Cartman0/rust_workspace_sample

RustのWorkspaceについて: Cargoのワークスペース - プログラミング言語 Rust

- Project
  - Cargo.toml // workspace用の設定
  - Cargo.lock // workpaceあたり1つもち,依存関係を書く
  - p1 // package1
    - Cargo.toml // package1用の設定
    - src
      - main.rs
      - lib.rs
  - p2 // package2
    - Cargo.toml // package2用の設定
    - src
      - main.rs // 今回はp1のlibも読み込めるようにする(依存関係)

まず、workspace用の設定を追加する。 workspace用(直下)のCargo.tomlファイルには以下のように書いてどのpackageをもつか明示する。

// /Cargo.toml
[workspace]

members = [
    "p1",
    "p2"
]

workspace用のCargo.lockにはpackage同士の依存関係を書く。 このdependenciesを書かないと外側のパッケージのファイルを見れずエラーになる。

// /Cargo.lock
[[package]]
name = "p1"
version = "0.1.0"

[[package]]
name = "p2"
version = "0.1.0"
dependencies = [
 "p1",
]

次に各パッケージの設定を追加する。

p1ディレクトリを作った後にその中でcargo initすればパッケージの初期設定が得られる。 (今回はライブラリファイルをもつのでcargo init --libでもいい)

Project> mkdir p1
cd p1
cargo init

p1/Cargo.tomlの中を確認する。 気をつけるべきは[package]となっているかとname[dependencies]の部分。 nameの部分がworkspaceの設定に使われる。今回は外部依存はないので[dependencies]は空のままでいい。

// p1/Cargo.toml
[package]
name = "p1"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

p1にはライブラリファイルを使いたいので以下のような公開モジュールにしておく。

// p1/src/lib.rs
pub mod my{
    pub fn hey(){
        println!("hey!");
    }
}

p1/src/main.rsはライブラリを読み込めるように以下のようにしておく。

use p1::my;

fn main() {
    println!("Hello, world! p1");
    my::hey();
}

次にp2の設定をする。 外部のp1に依存するのでpathをCargo.tomlに設定する。 このとき読み込むときの名前を変えられる。

//p2/Cargo.toml
[package]
name = "p2"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
p1 = { path = "../p1" }

p2/main.rsは次のようにする。外部ライブラリの読み込みもp1と同じように読み込める。

// p2/src/main.rs

use p1::my;

fn main() {
    my::hey();
    println!("Hello, world! p2");
}

以上でソースの設定はok。

デバッグの設定

次にVSCodeデバッグ・ビルドの設定をする。 設定ファイルは以下のように対応している。

  • ビルドのみ:コマンドか.vscode/tasks.json
  • デバッグ.vscode/launch.json

workspaceのビルドはProject直下で

cargo build
//or
cargo build --workspace

実際に試すと以下のような結果が得られ、targetディレクトリに実行ファイルが生成される。

vscode ➜ /workspaces/rust_project (main ✗) $ cargo build
   Compiling p2 v0.1.0 (/workspaces/rust_project/p2)
   Compiling p1 v0.1.0 (/workspaces/rust_project/p1)
    Finished dev [unoptimized + debuginfo] target(s) in 8.10s

パッケージごとにビルドしたい場合は-p <package name>をつける。

vscode ➜ /workspaces/rust_project (main ✗) $ cargo build -p p1
    Finished dev [unoptimized + debuginfo] target(s) in 0.09s

.vscode/launch.jsonを設定すればデバッグできる。 ただし、VSCodeのlldbデバッガでは1つの実行ファイルしか追えないのでcargo build --workspaceデバッグではエラーが出る。

f:id:cartman0:20220214220222p:plain
Error: Cargo has produced more than one matching compilation artifact

なのでデバッグのlaunch.jsonはpackageごとに設定すればいい。 "configurations":に追加していけばいい。 cargo属性中のargs属性の配列が実行されるコマンドになる。 また、"filter"の指定がないとエラーになる。

{
  ...,
  "configurations": [
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug 'p1' in 'rust_project'",
            "cargo": {
                "args": [
                    "build",
                    "-p",
                    "p1",
                    "--verbose"
                ],
                "filter": {
                    "kind": "bin"
                }
            },
            "args": [],
            "cwd": "${workspaceFolder}"
        },
        ...
        ],
        ...

VSCodeの「Run and Debug」からデバッグを実行できる。 1度実行するとmain関数上部にクリック可能なボタンが追加される。

f:id:cartman0:20220214220225p:plain
パッケージp1のデバッグ

ビルド用のタスク設定

デバッグしないのであればタスクとして、コマンドを.vscode/tasks.jsonに設定できる。 例えば,先程のworkspace全体のビルドを登録するとき次のように追加すればいい。

{
    ...,
    "tasks": [
        {
            "label": "rust: cargo build workspace",
            "type": "cargo",
            "command": "build",
            "problemMatcher": [
                "$rustc"
            ],
            "args": [
                "--workspace",
                "--verbose"
            ],
            "group": "build",
            "presentation": {
                "reveal": "always",  // Terminalパネルを開く
                "clear": true,       // 実行前にTerminalをクリア
            },
            "options": {
            }
        },
    ...],
    ...
}

"Terminal > Run Task"を選択すると登録したタスク一覧(labelに設定してタスク名)が出るので選ぶと実行される。。

f:id:cartman0:20220214220231j:plain
taskの選択

実行結果:

> Executing task: cargo build --workspace --verbose <

       Fresh p2 v0.1.0 (/workspaces/rust_project/p2)
       Fresh p1 v0.1.0 (/workspaces/rust_project/p1)
    Finished dev [unoptimized + debuginfo] target(s) in 0.75s

Terminal will be reused by tasks, press any key to close it.