packer.nvim から lazy.nvim に移行した後に nvim-treesitter が無限にインストールし続ける件

問題はタイトルの通りです。 ensure_installed を指定していると起動時にインストールが実行されるため、何度インストールが実行されても Neovim を再起動する度にまたインストールが実行されてしまう症状です。

原因

Neovim 内で &runtimepath を見てみると、 ~/.local/share/nvim/site が含まれています。 この中には packer とそれが管理するプラグインも格納されている ~/.local/share/nvim/site/pack/packer も含まれます。 これ以上は追っていないですが、おそらく nvim-treesitter がこのディレクトリ以下を見てインストールされているかどうかの判定を行なっているようでした。

対処方法

packer を完全に削除することで治ります。

1
rm -rf .local/share/nvim/site/pack

Neovide を WSL で使う (Neovim は mise で管理する場合)

Neovide を Windows で WSL 内の Neovim で使うときのメモです。Neovim を mise で管理している人向けです。

  • Neovide は Windows 向けをインストールする
  • Windows 側に Neovide の設定ファイルを用意する
  • WSL 内からは neovide.exe で起動する

ちょっと詳しく

Neovim を mise で管理している場合、Neovide から nvim が見えなくて起動時にエラーになってしまいます。 これを解決するために nvim のパスを設定ファイルに書いておきます。

Windows 向けの設定ファイルは %APPDATA%\neovide\config.toml に置きます。

1
2
neovim-bin = "$HOME/.local/share/mise/shims/nvim"
wsl = true

これで Neovide から nvim が見えるようになります。

自分の場合は WSL でしか使わないので wsl オプションもついでに有効化しておきます。 これによって neovide.exe --wsl ではなく neovide.exe だけで起動できるようになります。

Rust の shaku と mockall を組み合わせて使う例

shaku は DI ライブラリです。 日本語での情報が少なかったの詳細めに書いていきます。

mockall はモックライブラリです。 こちらは日本語での情報もすぐに見つかったので詳細は触れていないです。

まず shaku を使う

以降のコードスニペットにおいて use は省略しています。末尾にあるおまけの全体コードでは省略していないので気になる方はそちらを参照してください。 shaku の使い方については公式のガイドを見た方が当然詳しいですが、ここでも一通り使い方を書きます。

shaku を使うの最小の例

ここでは Hoge という trait で例示します。 trait を shaku で扱えるようにするには Interface の subtrait にします。

1
2
3
pub trait Hoge: Interface {
fn hoge(&self) -> String;
}

trait 側の準備はこれで完了です。

次に Hoge を実装した struct HogeImpl を用意します。 中身はてきとーです。 この HogeImpl の attribute に shaku(interface = Hoge) を指定することで、HogeImplHoge の解決に利用されることを示します。 そして derive(Component) することでボイラープレートを自動生成します。 Component の代わりに Provider を使うこともできますがそれは後述します。

1
2
3
4
5
6
7
8
9
#[derive(Component)]
#[shaku(interface = Hoge)]
pub struct HogeImpl;

impl Hoge for HogeImpl {
fn hoge(&self) -> String {
"hoge".to_string()
}
}

次に macro module! を使って依存解決に使う struct を作ります。

MyModule は自由に名前をつける部分です。 componentsproviders に、依存解決にどの struct を使うのかを指定します。Component である HogeImplcomponents に指定することで Hoge が解決できるようになります。

1
2
3
4
5
6
module! {
MyModule {
components = [HogeImpl],
providers = [],
}
}

これで使う準備は完了です。 テスト内で使ってみると以下のようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#[cfg(test)]
mod tests {
use shaku::HasComponent;

use super::*;

#[test]
fn test_hoge() {
let module = MyModule::builder().build();
let hoge: &dyn Hoge = module.resolve_ref();

let result = hoge.hoge();
assert_eq!(result, "hoge");
}
}

Component Hoge を依存に持つ例

上の例だと Hoge として HogeImpl を作っているだけなので何も嬉しくないです。 次に Hoge に依存する別のものを作ってみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pub trait Fuga: Interface {
fn fuga(&self) -> String;
}

#[derive(Provider)]
#[shaku(interface = Fuga)]
pub struct FugaImpl {
#[shaku(inject)]
hoge: Arc<dyn Hoge>,
}

impl Fuga for FugaImpl {
fn fuga(&self) -> String {
format!("fuga: {}", self.hoge.hoge())
}
}

今回は FugaImplComponent ではなく Provider としてみました。 2つの違いはざっくり以下です。

  • Component は MyModule のインスタンス内において1つのインスタンスを共有する
  • Provider は module.provide() の呼び出しの度に(内部での依存解決の度にも)新しくインスタンスが作られる

Component に依存する場合は Arc<dyn Trait> なフィールドを用意したうえで、その attribute に shaku(inject) を指定します。

module! 部分は以下のようになります。Provider の場合は providers に指定します。

1
2
3
4
5
6
module! {
MyModule {
components = [HogeImpl],
providers = [FugaImpl],
}
}

テスト内で使ってみると以下のようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#[cfg(test)]
mod tests {
use shaku::{HasProvider};

use super::*;

#[test]
fn test_fuga() {
let module = MyModule::builder().build();
let fuga: Box<dyn Fuga> = module.provide().unwrap();

let result = fuga.fuga();
assert_eq!(result, "fuga: hoge");
}
}

Provider の場合は module.provide() でインスタンスを作って Box<dyn Trait> で受け取ります。

ここでは unwrap していますが derive(Provider) で生成した分についてはエラーにはなり得ないです。
Provider を手動で実装して provide 内でエラーになり得るコードを書いた場合はエラーの可能性があります。

Provider Fuga を依存に持つ例

Component だけでなく Provider への依存も持つことができます。 上で作った Fuga を依存に持つものを作ってみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pub trait Piyo: Interface {
fn piyo(&self) -> String;
}

#[derive(Provider)]
#[shaku(interface = Piyo)]
pub struct PiyoImpl {
#[shaku(provide)]
fuga: Box<dyn Fuga>,
}

impl Piyo for PiyoImpl {
fn piyo(&self) -> String {
format!("piyo: {}", self.fuga.fuga())
}
}

Provider に依存する場合は Arc ではなく Box<dyn Trait> をフィールドに持たせて attribute shaku(provide) を付与します。 ちなみにProvider から Component に依存はできますが、逆の Component から Provider への依存はできません。

module! の定義にも追加したら利用できるようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module! {
MyModule {
components = [HogeImpl],
providers = [FugaImpl, PiyoImpl],
}
}

#[cfg(test)]
mod tests {
use shaku::HasProvider;

use super::*;

#[test]
fn test_piyo() {
let module = MyModule::builder().build();
let piyo: Box<dyn Piyo> = module.provide().unwrap();

let result = piyo.piyo();
assert_eq!(result, "piyo: fuga: hoge");
}
}

特定の実装を置き換える

上で作ったうち、他から依存されている HogeFuga の実装をテストにおいて置き換えてみます。 今回は mockall を使うので trait の attribute に cfg_attr(test, automock) を指定しておきます。 これによって MockHogeMockFuga が自動で作られます。

1
2
3
4
5
#[cfg_attr(test, automock)]
pub trait Hoge: Interface { ... }

#[cfg_attr(test, automock)]
pub trait Fuga: Interface { ... }

Component の実装を置き換える

テスト内で MockHoge を作って hoge が呼び出されたときに本来の実装とは別の値を返すようにしてみます。

1
2
3
4
let mut mock_hoge = MockHoge::new();
mock_hoge
.expect_hoge()
.returning(|| "mocked_hoge".to_string());

module builder において with_component_override を使って Hoge の上書きをする設定を行ないます。

1
2
3
let module = MyModule::builder()
.with_component_override::<dyn Hoge>(Box::new(mock_hoge))
.build();

この module を使って依存解決を行なうとHoge として mock_hoge が使われるようになります。 実行結果を見ると mock_hoge が使われていることがわかります。

1
2
3
let fuga: Box<dyn Fuga> = module.provide().unwrap();
let result = fuga.fuga();
assert_eq!(result, "fuga: mocked_hoge");

Provider の実装を置き換える

Provider を置き換えるには module builder の with_provider_override を使います。 これの引数は Fn になっているので、クロージャ外で mock_fuga を作って move で渡しても所有権の問題(Fn は複数回呼ばれうるので返り値にするものが Clone できないと外から渡せない)でコンパイルが通りません。 なのでクロージャ内で作っています。

1
2
3
4
5
6
7
8
9
let module = MyModule::builder()
.with_provider_override::<dyn Fuga>(Box::new(|_| {
let mut mock_fuga = MockFuga::new();
mock_fuga
.expect_fuga()
.returning(|| "mocked_fuga".to_string());
Ok(Box::new(mock_fuga))
}))
.build();

こちらのパターンでも別の実装に置き換えることができました。

1
2
3
let piyo: Box<dyn Piyo> = module.provide().unwrap();
let result = piyo.piyo();
assert_eq!(result, "piyo: mocked_fuga");

まとめというか感想

  • shaku を使って DI まわりのボイラープレートを最小限にできた
  • shaku 自体は非常に小さいライブラリなのでもしも問題があっても自分で対処できそう
  • shaku と mockall の組み合わせで特に問題になることもなく普通に使えた

おまけ

最終形のコードの全体は以下です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
use std::sync::Arc;

use shaku::{module, Component, Interface, Provider};

#[cfg(test)]
use mockall::{automock, predicate::*};

#[cfg_attr(test, automock)]
pub trait Hoge: Interface {
fn hoge(&self) -> String;
}

#[derive(Component)]
#[shaku(interface = Hoge)]
pub struct HogeImpl;

impl Hoge for HogeImpl {
fn hoge(&self) -> String {
"hoge".to_string()
}
}

#[cfg_attr(test, automock)]
pub trait Fuga: Interface {
fn fuga(&self) -> String;
}

#[derive(Provider)]
#[shaku(interface = Fuga)]
pub struct FugaImpl {
#[shaku(inject)]
hoge: Arc<dyn Hoge>,
}

impl Fuga for FugaImpl {
fn fuga(&self) -> String {
format!("fuga: {}", self.hoge.hoge())
}
}

#[cfg_attr(test, automock)]
pub trait Piyo: Interface {
fn piyo(&self) -> String;
}

#[derive(Provider)]
#[shaku(interface = Piyo)]
pub struct PiyoImpl {
#[shaku(provide)]
fuga: Box<dyn Fuga>,
}

impl Piyo for PiyoImpl {
fn piyo(&self) -> String {
format!("piyo: {}", self.fuga.fuga())
}
}

module! {
MyModule {
components = [HogeImpl],
providers = [FugaImpl, PiyoImpl],
}
}

#[cfg(test)]
mod tests {
use shaku::{HasComponent, HasProvider};

use super::*;

#[test]
fn test_hoge() {
let module = MyModule::builder().build();
let hoge: &dyn Hoge = module.resolve_ref();

let result = hoge.hoge();
assert_eq!(result, "hoge");
}

#[test]
fn test_fuga() {
let module = MyModule::builder().build();
let fuga: Box<dyn Fuga> = module.provide().unwrap();

let result = fuga.fuga();
assert_eq!(result, "fuga: hoge");
}

#[test]
fn test_fuga_mock() {
let mut mock_hoge = MockHoge::new();
mock_hoge
.expect_hoge()
.returning(|| "mocked_hoge".to_string());

let module = MyModule::builder()
.with_component_override::<dyn Hoge>(Box::new(mock_hoge))
.build();
let fuga: Box<dyn Fuga> = module.provide().unwrap();

let result = fuga.fuga();
assert_eq!(result, "fuga: mocked_hoge");
}

#[test]
fn test_piyo() {
let module = MyModule::builder().build();
let piyo: Box<dyn Piyo> = module.provide().unwrap();

let result = piyo.piyo();
assert_eq!(result, "piyo: fuga: hoge");
}

#[test]
fn test_piyo_mock() {
let module = MyModule::builder()
.with_provider_override::<dyn Fuga>(Box::new(|_| {
let mut mock_fuga = MockFuga::new();
mock_fuga
.expect_fuga()
.returning(|| "mocked_fuga".to_string());
Ok(Box::new(mock_fuga))
}))
.build();
let piyo: Box<dyn Piyo> = module.provide().unwrap();

let result = piyo.piyo();
assert_eq!(result, "piyo: mocked_fuga");
}
}

YAPC::Hiroshima 2024 に参加してきました

久し振りに YAPC へ行ってきました。 広島へは行ったことがなかったので旅行も兼ねていました。 ここ数ヶ月、半休職的な感じで勤務数を減らして体調を整え中だったため体力具合の確認も兼ねたりしていました。

広島でコロナを貰ってしまい大ダウンしていたので軽くの感想です。

YAPC 編

前夜祭、本編、YAYAPC と一通り参加してきました。 以下、いくつか感想をつらつら書いていきます。

songmu さんのやつ

個人的にインパクトが強かったのは songmu さんが YAYAPC で話していたやつです。こういう話は大人数がいるところではなかなかしないから貴重でした。内容は非公開なのでぼやかしつつ、自分の感想としては↓でした。 あまり効果がない的な流れでしたがあるところではあることを表明しておきます。もっと夢もあります。

関数型プログラミングと型システムのメンタルモデル

naoya さんの関数型の話が良いなーと思っていたら、ちょうど似たようなタイミングで Elixir の記事がすこし注目されていたり、Elixir 実践入門の発売が迫っていたりで Elixir をやってみることにしました。発表では Elixir だったわけではないですが Elixir は前から気になっていた言語だったので良いきっかけになりました。

Elixir 実践入門が発売される直前に先にすごいE本で Erlang もやり始めてみました。コロナでダウンしている間に横になって読んでいた程度なのでまだがっつりはできていないです。 新しい言語をやるのは久し振りでした。以前は頻繁にいろいろ本を写経してたのが最近はあまりできていませんでした。そういう意欲が回復したのは大きな収穫だったかもしれません。

My Favorite Protocol: Idempotency-Key Header

以前「こういうのが必要だよなー」と自分でも考えていたものが、やはり多くの人が必要としていたことが知れて安心?しました。規格化を目指して話が進んでいるとのことなので必要な場面に遭遇したら使っていきます。

キーノート

生のとほほさんを見られてとにかく感動でした!!

アウトプットは自然とやっているというのがやっぱ強いなと。伝説的な人は大抵「好きこそ物の上手なれ」勢だなーと思いました。

トーク後にその辺で質問にあっているのを聞いていたら Vimmer であることがわかって嬉しかったです。当日は使用エディタの質問が出たときは Emacs ばかりという流れがありました。

旅行編

前夜祭の日中は尾道へ行ってきました。

↓は新幹線からの乗り換えですこしだけ乗った特殊な電車です。 エトセトラという観光列車で、広島駅と尾道駅の間を走っています。 今回はほんのすこしだけ乗った形でしたが、いつかまた広島へ行ったらフルで乗ってみたいなと思っています。

↓は尾道で食べた尾道ラーメンです。ゴロゴロした背脂が特徴らしいです。

↓は尾道の千光寺の一部の眺めです。 山に沿って作られていて全体的にすごい構造をしていました。

↓は千光寺周辺を下っているときの通路です。 めちゃくちゃ入り組んだ構造になっているのがめちゃ好みでした。

前夜祭前には原爆ドームへ行きつつ↓の動画の場所も見てきました。

↓は YAYAPC の翌日、東京へ帰る前に宮島(厳島)へ行ってきたやつです。厳島神社の鳥居です。だいぶ潮が引いていたのですぐ近くまで行けました。

コロナ編

帰りの新幹線に乗っている途中から喉が怪しく痛みだし、翌日の夕方から強烈に発熱してコロナで確定しました。

1月の頭に XBB.1.5 のワクチンを接種したばかりでもダメでした。今流行しているのは JN.1 という株のようです。

抗ウイルス薬はせっかくなので処方してもらいました(劇的な効果があるわけではなく治るのがすこし早くなる程度という説明はされつつ)。去年までは無料だったけど今は有料で3割負担の人は9000円くらいでした。 カプセルが大きく1回4個(1日2回)飲むのが辛かったです。結局長引いてしまったし処方してもらわなくて良かったな……という感想です。

この記事を書いている3月2日現在ではコロナの症状は回復しています。 ただし空咳がけっこう残っています。これは普通の風邪をひいたときも咳だけ長びくので同様なのかなと思っています。気管支弱め人材です。 発熱前症状の喉の痛みとは別な感じで喉の違和感が残ってもいます。たまに急に喉がイガイガするようにもなっています。

自分が罹患した感想としては「ただの風邪では断じてない」というものでした。

その他

新幹線や長時間の電車での移動中、ホテルで横になって休んでいるときには、買ってから積んでいたシレン6をやっていました。 広島から帰ってくる前になんとかストーリークリアまで漕ぎ着けました。 最近はちまちまクリア後ダンジョンをクリアしていき神髄に向けて心の準備を進めています。

Ubuntu 22.04 で Sorbet をビルドする

Ubuntu 22.04 の環境で Sorbet のビルドをしたらエラーが発生しました。 単純な問題ですが一応書き残しておきます。

README にあるように ./bazel build //main:sorbet --config=dbg を実行したら以下のエラーが発生しました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ./bazel build //main:sorbet --config=dbg
WARNING: Download from https://mirror.bazel.build/www.colm.net/files/ragel/ragel-6.10.tar.gz failed: class java.io.FileNotFoundException GET returned 404 Not Found
WARNING: Download from https://mirror.bazel.build/ftp.gnu.org/gnu/m4/m4-1.4.18.tar.xz failed: class java.io.FileNotFoundException GET returned 404 Not Found
WARNING: Download from https://mirror.bazel.build/github.com/jmillikin/rules_m4/releases/download/v0.1/m4-gnulib-788db09a9f88abbef73c97e8d7291c40455336d8.tar.xz failed: class java.io.FileNotFoundException GET returned 404 Not Found
WARNING: Download from https://mirror.bazel.build/ftp.gnu.org/gnu/bison/bison-3.3.2.tar.xz failed: class java.io.FileNotFoundException GET returned 404 Not Found
WARNING: Download from https://mirror.bazel.build/github.com/jmillikin/rules_bison/releases/download/v0.1/bison-gnulib-788db09a9f88abbef73c97e8d7291c40455336d8.tar.xz failed: class java.io.FileNotFoundException GET returned 404 Not Found
INFO: Analyzed target //main:sorbet (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
ERROR: /home/mihyaeru/.cache/bazel/_bazel_mihyaeru/a74a2d0f7000d949282952fb8eb1689b/external/com_google_absl/absl/strings/BUILD.bazel:500:11: Compiling absl/strings/internal/cordz_functions.cc [for host] failed: (Exit 127): clang failed: error executing command /home/mihyaeru/.cache/bazel/_bazel_mihyaeru/a74a2d0f7000d949282952fb8eb1689b/external/llvm_toolchain_12_0_0/bin/clang -U_FORTIFY_SOURCE -fstack-protector -fno-omit-frame-pointer -fcolor-diagnostics ... (remaining 71 arguments skipped)
/home/mihyaeru/.cache/bazel/_bazel_mihyaeru/a74a2d0f7000d949282952fb8eb1689b/external/llvm_toolchain_12_0_0/bin/clang: error while loading shared libraries: libtinfo.so.5: cannot open shared object file: No such file or directory
ERROR: /home/mihyaeru/ghq/github.com/mihyaeru21/sorbet/common/concurrency/BUILD:1:11: Compiling common/concurrency/WorkerPoolImpl.cc [for host] failed: (Exit 127): clang failed: error executing command /home/mihyaeru/.cache/bazel/_bazel_mihyaeru/a74a2d0f7000d949282952fb8eb1689b/external/llvm_toolchain_12_0_0/bin/clang -U_FORTIFY_SOURCE -fstack-protector -fno-omit-frame-pointer -fcolor-diagnostics ... (remaining 54 arguments skipped)
/home/mihyaeru/.cache/bazel/_bazel_mihyaeru/a74a2d0f7000d949282952fb8eb1689b/external/llvm_toolchain_12_0_0/bin/clang: error while loading shared libraries: libtinfo.so.5: cannot open shared object file: No such file or directory
Target //main:sorbet failed to build
Use --verbose_failures to see the command lines of failed build steps.
INFO: Elapsed time: 0.243s, Critical Path: 0.03s
INFO: 24 processes: 24 internal.
FAILED: Build did NOT complete successfully

libtinfo.so.5 が入っていないようです。 以下で入れてあげたら通るようになりました。

1
sudo apt install libtinfo5

トラックボール付き Dactyl Manuform を作った

完成品

背景

同僚が Dactyl Manuform を使っていたのが存在を知ったきっかけでした。 話しているとトラックボール付きもあることまでわかり一気に欲しくなりました。

以下のような要件で作っていくことを決めました。

  • Dactyl Manuform
  • トラックボールは右手側
  • 右手は4x7(Dactyl Manuform としては5x7)
  • 左手は4x6(同じく5x6)

やったこと

3Dプリントするためのモデル探し

Dactyl Manuform は、パラメータを調整してスクリプト実行することで3Dモデルが生成されるスタイルになっています。 bullwinkle3000 さんがトラックボールを付けられるようにしたものを公開してくれています。 そのリポジトリまでの fork 関係はこのようになっています。

今回はこれをさらに fork してちまちま設定、コード、モデルに変更を加えました。
https://github.com/mihyaeru21/dactyl-keyboard

コードをそのまま持ってこなくてもオンラインでパラメータの調整ができるサイトもいくつかあります。ただしトラックボールは付けられません。

各種パラメータを変更するとどのような結果になるのかをまとめてくれているところもあります。 ただし dactyl-keyboard は fork が繰り返されており、どれに対するパラメータなのか正確に把握できていません。
https://github.com/yejianfengblue/dactyl-generator-demo

モデルの調整

fork してきてスクリプトを実行しようとしたところ Docker での実行が壊れているようで最初にハマりました。 コード化まではしていませんが、とりあえずコンテナを作って正常に実行ができる環境を作る方法を README に記載してあります。 src/run_config.json を変更して実行することでいろいろ試すことができます。

最初は OpenSCAD 形式で出力する設定になっていました。出力後のモデルを表示、編集するために OpenSCAD にすこし入門もしてみました。 ところが OpenSCAD ではなく OpenCASCADE 形式での出力の方が新しくなっていたことに時間が経ってから気付きました。
出力の確認と編集のために FreeCAD を使い始めてみました。

パラメータの調整と生成コードの変更によって右手のトラックボール周辺は通常の形状に極めて近いレイアウトをがんばって自作してみました(左)。

親指部分を自作したモデル fork 元リポジトリのデフォルトのモデル

良い感じになったので、これで DMM.make にてプリントを発注してみました。 ところが厚さが足りない(最小 1mm)部分があるとサポートから連絡が来てキャンセルになってしまいました。 スクリプトを1回実行するのに地味に時間がかかったり、どう変更するとどこが変わるのかが難しくこの時点でかなり時間をかけてしまっていていたので、とりあえず fork 元リポジトリのデフォルトのモデルで作ってみることにしました。

3Dプリンタを購入

自宅用と会社用と作る練習用で少なくとも3台は作ろうと思っていたのですが、DMM.make では両手合わせて2万円を越える見積りになってしまい「これなら3Dプリンタを買ってしまっても良いのでは?」という気持ちがすこし芽生えていました。 ここくらいのときに盛大に体調を崩してしまい2週間ほど仕事を休んでしまっていました。その休んでいる間はほとんど横になって過しており YouTube で受動的に動画を観るだけの生活をしていました。 そのときに3Dプリンタの動画を観すぎた影響で異常に物欲が刺激されていて、体調が回復してきた祝いにポチってしまいました。

買ったのは Creality Ender-5 S1 です。 Amazon 的にはこれです(アフィ登録できてないのただのリンクです🙄)。 買ったときは10%オフくらいになっていて7万円台前半でした。 同じく10万以下クラスで高速プリントができる AnkerMake M5 と迷ったのですが、YouTube のいろいろな動画を見た限りでは Ender-5 S1 の方が綺麗に出力できているように見えたのでこちらにしました。 値段が3万円弱安かったのもあります。

パーツを作成

Dactyl Manuform ではコントローラー(Pro Micro)や左右を繋ぐケーブルの接続部を格納する方式として取り外し可能なホルダーを選択できます。 このホルダーにリセット用のスイッチを付けたくてホルダー自体を自作することにしました。

これを行なっているときは3Dプリンタが手元にあるメリットを最大限享受できまた。 それっぽいサイズで実際にプリントしてから実際に Pro Micro などをはめ込んでみて微調整を行なう流れが良かったです。 実際にプリントしてみると余白具合が想定と違って修正が必要だったりしました。

モデル内側 モデル外側 実際にはめ込まれている図

部品集め

部品集めと組み立てには https://github.com/Schievel1/dactyl_manuform_r_track の情報がとても参考になりました。 ただしトラックボールを配置する側の Arduino Micro は不要で左右両方とも Pro Micro で十分でした。

Pro Micro やホットスワップのソケットなど元々持っていたものは記載していません。 わりと個人的なメモでもあるので今回追加で買ったものを列挙してあります。 全体像を掴むには↑と合わせて見ると良いです。 ちょうど良いものを見つけるのが地味に面倒でした。

センサーモジュールのお試し

本体をプリントする前にセンサーが届いたのでさっと動作確認をしてみようとラズパイに繋いで動かそうとしてみました。 そもそも電子回路の知識もぜんぜんなくて SPI 通信という言葉も初めて聞いたようなレベルだったので雰囲気だけ覚えて撤退してしまいました。 QMK Firmware にトラックボールを動作させる機能があるのでそれだけ動かしてみれば良いことに気付いたのでそちらにてやることにしました。

https://qiita.com/mizuhof/items/0c34308c2d57c9345f3a を参考に最小限のキーボードのファームウェアを用意して、そこにトラックボールの設定を追加することで実現しました。 雑にブレッドボードでそれっぽい感じでキーボード側の動作確認をしつつトラックボールのセンサーが動くことも確認しました。よく見るとスイッチングダイオードがないのでキーの方は挙動があれですがスルーで。

センサーのお試し

本体をプリント

STL ファイルをスライサーに食わせて3Dプリンタ向けの形式に変換します。

スライサーの画面

右手は21時間、左手は19時間かかりました。これでもスライサーに表示されていた予想時間よりそれぞれ3時間ほど早く終わりました。

右手をプリント中 右手のプリント完了
左手をプリント中 左手のプリント完了

プリントされたての状態では大量のサポート材がついているので頑張って除去します。この作業が辛すぎました。先にやった右手はニッパーとペンチだけでだらだら動画を見ながら通話しながらやって合計5時間くらいかかってしまいました。さすがにやばすぎたので左手はドリルを使うようにして1時間半ちょいくらいで除去できました。

プリント直後 すこし除去後 除去完了

参考にしていた https://github.com/Schievel1/dactyl_manuform_r_track ではこの時点でヤスリがけをして表面の積層痕を消したうえで塗装もしていました。元々は同様の手順でやる予定でしたが、サポート材の除去で疲れすぎて今回は諦めてしまいました。2台目を作るときはこれにもチャレンジします。

組み立て

まずセンサーモジュールが取り付けられるのか確認してみました。

この取り付け部分はパーツをすこし改造してねじ止めしやすいようにしたのですが、そのときに前提条件を勘違いしており厚みが1mm足りずセンサーが正常に動作しませんでした。これだけのためにまたプリントするのは無理なので厚紙とプラスチック片でゲタを履かせることでなんとか問題を回避しました。

ビットインサートをつけた 基盤にジャンパーワイヤをはんだ付けした ねじで固定

この後はだいたい以下のような流れで作りました。

  • 蓋用のインサートビットをはんだごてを使って入れる
  • 無限の可能性にスイッチングダイオードとホットスワップソケットをはんだ付けしてバラバラにする
  • 本体にキースイッチを装着する
  • そこに無限の可能性をはめる
  • row, col のうち接続すべき箇所を繋ぐ
    • 左手は普通の Dactyl Manuform と同じ
    • 右手はピンの節約のためにちょっと独特にした
  • キースイッチを抜いたときに位置が固定されるようにホットボンドで接着させる
  • ホルダーに Pro Micro とリセットスイッチとTRRSジャックをセットする
  • 各種ワイヤを Pro Micro の正しいピンに差し込む
  • ホルダーを本体にセットして蓋を閉めて物理的には完成!
無限の可能性をはめた 配線を終えた Pro Micro と接続してホルダーをはめた

問題

ホルダーにはでっぱりを、本体側には溝を用意して前後がずれないようにしてみたのですが、その幅が1mmで小さすぎたためにそのままでははまりませんでした。これはかなり頑張ってヤスリで調整しました。

あとで発覚したものですがホットスワップソケットのはんだ付け不良が5箇所くらい見つかりました。本体にセットする前に1つずつチェックしておくべきでした。

ジャンパーワイヤが内部で断線しているケースも1つだけありました。

ファームウェア

QMK Firmware については他のところでも情報があるしさらっとしておきます。 ここに今回作ったやつを置いてあります。
https://github.com/mihyaeru21/qmk_firmware/tree/mihyaeru/keyboards/handwired/dactyl_manuform/mb_v1

トラックボールというかポインティングデバイスについてはこのドキュメントを参照します。
https://github.com/qmk/qmk_firmware/blob/master/docs/feature_pointing_device.md

以下ができるので通常のマウスというかトラックボールでできることは全部できるはずです。

  • 左・中・右クリック
  • 上下・左右のスクロール
  • 戻る・進むボタン

完成してみて

雑にいろいろ書き出し

(メモしていたところからコピペ)

  • controller holder にすると半田付けが辛すぎた
    • TRRS とタクトスイッチは基盤つきのやつにした方が良い
    • そうなると holder ではなく直接つける方式でも良さげ
  • 無限の可能性に半田付けした時点で電気的な疎通確認をしておいた方が良い
    • ワイヤーの疎通確認も必要
  • WILD の下2つのボタンは押しづらすぎる
  • 右小指が遠い
    • 人差し指内側にもう1列追加する方が良いのかも
    • 小指は内側に反らせるとか良いかも
  • マウス用途にレイヤーをトグルするケースを想定するとディスプレイがあるのも良い気がしてきた
  • パームレストを接続したい
  • 左右の位置を固定するソリューションが欲しい気がする
  • 蓋はプリントしてすぐ剥がすと反ってしまうので冷めてからにする
  • 蓋を固定するためのネジ穴が小さかった
  • ヤスリがけと塗装はやりたい
  • 逆さまにしてもボールが落ちないようにしたい
  • トラックボールモジュールを固定する部分の厚さがダメだった
  • ハンドワイヤリングが辛すぎ
  • 固定用ボンドは動作確認が完了してからの方が良かった
  • トラックボールはベアリングだと微妙
  • ダイオードは表面実装の方が望ましい
  • キーピッチはもうちょい狭くしたい

感想

けっこう遠回りしていろいろやった気がします。 そのおかげで道具もいろいろ揃えたし経験も積めました。 実際に完成したものを使ってみて改善したい点がたくさんあるのでゴールは遠そうですし、いろいろ作ってみたい気持ちになっています。

ちょうど1週間、仕事含めて実際に使っています。湾曲していたり配列が普通と違う部分があってまだまだ体が慣れないです。冒頭の写真は左右を1枚に収めるためにすぐ近くに置いていますが実際には肩幅くらい離しています。これによって肩が丸まらないようになってきたような……🤔

一番の目的だったトラックボールは理想的な触り心地ではないもののめちゃくちゃ便利です。図形描画のような細かいマウス操作を多様するもの以外はほぼすべてキーボードに付属のトラックボールで余裕でやっていけています。PC に向かっている間、両手はほぼ常にキーボードのところにあります。ここについては想定通りになって安心しました。

この記事を書きつつ今回プリントしたモデルを Elecrow の自動見積りに投げてみたら一番安いレジンで$60くらい(送料別)であることがわかってしまいました。 3Dプリンタを買った理由の一部がなくなってしまいましたが試作パーツを手元でプリントできるようになったのは想像以上に良かったので気にしないことにします。

nvim-lspconfig で Bundler を考慮するプラグインを作った

背景

Neovim のビルトイン LSP クライアントを使っている人は今だと nvim-lspconfig を使っている人が多いです。

LSP サーバーのデフォルトの起動コマンドは、グローバルにインストールされたものを見るようになっています。 例えば ruby-lsp の場合は ruby-lsp というコマンドになります。 これは Bundler を考慮してくれないので ruby-lsp を Bundler 経由で使っているプロジェクトにおいても bundle exec ruby-lsp ではなく ruby-lsp が利用されます。 一応、設定でコマンドを変更することができます。 nvim-lspconfig では ruby-lsp は ruby_ls という名前で設定します。 以下は bundle exec ruby-lsp で起動する設定例です。

1
2
3
4
5
6
local lspconfig = require('lspconfig')
lspconfig.ruby_ls.setup {
...
cmd = { 'bundle', 'exec', 'ruby-lsp' },
...
}

しかしこれだと Bundler を使っていないプロジェクト(ディレクトリ)においても bundle exec ruby-lsp が利用されてしまいます。 無駄にエラーが出るようになって地味にうるさいです。

作った

この問題を解決するために nvim-lspconfig-bundler というプラグインを作りました。

このプラグインを入れて設定を1行追加すると nvim-lspconfig で入れた Ruby の LSP サーバー実行コマンドに必要に応じて bundle exec を付与します。 以下のように lspconfig.hoge.setup よりも前に require('lspconfig-bundler').setup() を一度だけ呼び出しておけば OK です。 require('lspconfig') の前でも大丈夫なはず(プラグイン内部で require しているので変わらないはず )です。

1
2
3
4
5
6
7
local lspconfig = require('lspconfig')

require('lspconfig-bundler').setup()

lspconfig.ruby_ls.setup {
...
}

仕組み

nvim-lspconfig には setup の実行時にフックを仕込む機構が用意されています。 この仕組みについては :h lspconfig-setup-hook に記載があります。具体的な setup が呼び出される前に以下のようにしておくことでフックを仕込むことができます。config はデフォルト値が加味された後の設定を表現するテーブルです。これが変更可能なので実際の読み込みの前にいろいろできるという寸法です。

1
2
3
lspconfig.util.on_setup = lspconfig.util.add_hook_before(lspconfig.util.on_setup, function(config)
...
end)

そのフックの中で既知の Ruby の LSP の場合のみ判定を行ない、必要があれば cmd を書き換えています。 判定はわりとナイーブに Gemfile.lock がある場合にその中を見てコマンドを提供する gem が含まれているかどうかで行なっています。

感想

地味に便利になって満足度が高いです✌️

nvim-lspconfig 側で良い感じにやってほしい機能なので要望はありますが現時点では実装されていなかったので独自にプラグインとして作ってみました。 そしてその issue に宣伝しておきましたw 需要があるようなら本体側に実装するのもありかなと思っています。

ブログのホスティングを S3 から Vercel に変更しました

タイトルの通り、このブログのホスティングを S3 + CloudFront から Vercel へ乗り換えました。

ジェネレータを Hexo から Astro に乗り換えようと思い、小出しでいろいろ更新していくことにしました。 Hexo も Node もめちゃ古いバージョンで動かしていました。 その理由が Hexo のコンテンツを S3 にデプロイするパッケージが新しいバージョンに対応していなくて代替案を考えるのが面倒で放置していました。 ホスティングを Vercel に変更したらデプロイ周りが楽になりつつ自前でデプロイの面倒を見なくてよくなるので、まずこの移行をやることにしました。

Vercel は Hexo のデプロイに対応しているので何も設定せずリポジトリを Vercel にインポートするだけで動きました。便利すぎ!!

デプロイ後、Settings → Domains から blog.mihyaeru.com を追加し、Route 53 にて CNAME を変更することで向き先を変更して移行完了です。

MariaDB に PR を出してみた

経緯

ダイレクトマーケティング?が飛んで来て、内容がガチでやるだけっぽい雰囲気だったのでとりあえず開発環境を準備してやってみることにしました。(元同僚なノリでメンション来てます。「別の元同僚が PR 出してくれた」的なツイートをファボったら白羽の矢が立った感じです。)

準備

まず GitHub で fork します。 今回はそのまま fork すると mihyaeru21/server になってわかりにくかったので mihyaeru21/MariaDB に名前を変更してみました。

ビルドするために公式のドキュメントに従っていろいろ入れていきます。 手元の環境が WSL2 上の Ubuntu 20.04 なので Ubuntu 向けの情報を参照しています。 説明だと 10.3 だけど現在のデフォルトブランチが 10.9 なので、ところどころ置き換えてやっていきます。

1
2
3
4
5
6
$ sudo apt install software-properties-common devscripts equivs
$ sudo apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xF1656F24C74CD1D8
$ sudo add-apt-repository --update --yes --enable-source 'deb [arch=amd64] http://nyc2.mirrors.digitalocean.com/mariadb/repo/10.9/ubuntu '$(lsb_release -sc)' main'
$ sudo apt build-dep mariadb-10.9
Reading package lists... Done
E: Unable to find a source package for mariadb-10.9

先生(@NayutaYanagisaw)に聞いたところ、 10.9 をビルドする分には 10.8 のやつでも問題ないとのことなので以下に変更してやり直しました。

1
2
$ sudo add-apt-repository --update --yes --enable-source 'deb [arch=amd64] http://nyc2.mirrors.digitalocean.com/mariadb/repo/10.8/ubuntu '$(lsb_release -sc)' main'
$ sudo apt build-dep mariadb-10.8

これは通りました。

fork したリポジトリを落してきてビルドするスクプトを実行してみました。

1
2
3
$ ghq get -p mihyaeru21/MariaDB
$ # 該当ディレクトリに移動
$ ./debian/autobake-deb.sh

無事にビルドできましたが、実行後に大量に diff が生み出されていました。バージョン的な変更も入っていたのでリリース用のスクリプトなのかもしれません。詳しくは見ていないです。

先生の記事では以下のようにやっていました。

1
2
3
$ mkdir mariadb-server/bld && cd $_
$ cmake -DCMAKE_BUILD_TYPE=Debug ..
$ cmake --build . --config Debug -j 4

自分の環境に合わせて以下で実行しました。こっちの方がかなり速く完了します。やっぱ ./debian/autobake-deb.sh はビルド以外にもいろいろやっているようです。

1
2
3
$ mkdir build && cd $_
$ cmake ..
$ cmake --build . --config Debug -j 16

今回のチケット

チケットは https://jira.mariadb.org/browse/MDEV-28364 です。

「Fix Version/s:」が 10.10 なので 10.10 ブランチで作業します。

1
$ git checkout -t origin/10.10

変更を入れる前にビルドできることを確認するためにあらためてビルドしておきます。

1
2
$ cmake ..
$ cmake --build . --config Debug -j 16

今回は無駄なコードを削除するタスクでした。対象を検索してみると修正箇所は3箇所だけでした。

1
2
3
4
5
6
7
8
9
$ rg SPIDER_HAS_EXPR_CACHE_ITEM
storage/spider/spd_db_conn.cc
7938:#ifdef SPIDER_HAS_EXPR_CACHE_ITEM

storage/spider/spd_db_include.h
36:#define SPIDER_HAS_EXPR_CACHE_ITEM

storage/spider/spd_db_mysql.cc
6390:#ifdef SPIDER_HAS_EXPR_CACHE_ITEM

10.10 の時点では常に #define SPIDER_HAS_EXPR_CACHE_ITEM される状態になっているので #ifdef 内部を残すように #ifdef を削除すれば OK でした。

その後、変更によって壊れていないことを確認するために再度ビルドして通ることを確認しました。ちなみに、おかしくなっている場合にちゃんとコケることも確認してみました。

1
$ cmake --build . --config Debug -j 16

テストも動かしてみようかと一瞬思ったのですが CI で実行しているのでそっちに任せることにしました。

PR へ

今回作った PR は https://github.com/MariaDB/server/pull/2150 です。

PR を作ったら CLAassistant というボットからコメントがついて、CLA に同意しろという内容でした。そんなに OSS 活動をしていなかったので「なんぞや」と思ったのですが大きいプロジェクト(企業のやつ?)だとよくあるっぽいです。

選択肢としては以下の2つがあったのですが、とくにこだわりはないのでおすすめっぽい1つ目の「MariaDB Contributor Agreement」にしました。

CLA の方の手続きを終えると CI が動き始めました。今回の PR では base branch の時点でコケるようになっていたタイミングだったので1つ通らなかったですが、それがわかっていたのでマージしてもらいました。

感想

案外楽に環境を整えてコードをいじるところまで辿り着けました。ドキュメントがしっかりしていると参入?障壁が低いなーという感想です。仕事でもちゃんとドキュメントを書いていかないと……。

コードをいじるときは Neovim で ccls を使ってみたのですが設定がうまくできていないのか、コードジャンプがあまり良い感じに動きませんでした。原因調査するよりはタスクを完了させたかったので深追いはしていないです。もうすこし本格的にいじる機会があればちゃんと追ってみようかなと思います。

AWS SDK for Rust の複数サービスのエラーをまとめる

公式の aws-sdk-rust を使うときの話です。まだ pre-release なので今後のバージョンでは話が変わるかもです。現在の最新である 0.10.1 での話です。

初期状態

もともとこんな感じでアプリケーションのエラーをまとめた enum を用意していました。これなら、main でやっているように ? が使えます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#[derive(thiserror::Error, Debug)]
enum MyError {
#[error("some application error has been occurred.")]
AppHoge,

#[error("aws api error has been occurred")]
Aws(#[from] aws_sdk_ec2::Error),
}

impl<T> From<aws_sdk_ec2::types::SdkError<T>> for MyError
where
aws_sdk_ec2::types::SdkError<T>: Into<aws_sdk_ec2::Error>,
{
fn from(e: aws_sdk_ec2::types::SdkError<T>) -> Self {
MyError::Aws(e.into())
}
}

#[tokio::main]
async fn main() -> Result<(), MyError> {
let config = aws_config::from_env().load().await;
let ec2 = aws_sdk_ec2::Client::new(&config);

let ec2_result = ec2.describe_instances().send().await?;

// do something

Ok(())
}

問題発覚

EC2 以外の別サービスの呼び出しを追加した状態です。ここでは S3 の呼び出しを追加してあります。

1
2
3
4
5
6
7
8
9
10
11
12
13
#[tokio::main]
async fn main() -> Result<(), MyError> {
let config = aws_config::from_env().load().await;
let ec2 = aws_sdk_ec2::Client::new(&config);
let s3 = aws_sdk_s3::Client::new(&config);

let ec2_result = ec2.describe_instances().send().await?;
let s3_result = s3.list_buckets().send().await?;

// do something

Ok(())
}

これはコンパイルできなくて以下のエラーになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
error[E0277]: `?` couldn't convert the error to `aws_sdk_ec2::Error`
--> src/main.rs:26:51
|
26 | let s3_result = s3.list_buckets().send().await?;
| ^ the trait `From<SdkError<ListBucketsError>>` is not implemented for `aws_sdk_ec2::Error`
|
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
= help: the following implementations were found:
<aws_sdk_ec2::Error as From<SdkError<AcceptReservedInstancesExchangeQuoteError, R>>>
<aws_sdk_ec2::Error as From<SdkError<AcceptTransitGatewayMulticastDomainAssociationsError, R>>>
<aws_sdk_ec2::Error as From<SdkError<AcceptTransitGatewayPeeringAttachmentError, R>>>
<aws_sdk_ec2::Error as From<SdkError<AcceptTransitGatewayVpcAttachmentError, R>>>
and 518 others
= note: required because of the requirements on the impl of `Into<aws_sdk_ec2::Error>` for `SdkError<ListBucketsError>`
= note: required because of the requirements on the impl of `From<SdkError<ListBucketsError>>` for `MyError`
= note: required because of the requirements on the impl of `FromResidual<Result<Infallible, SdkError<ListBucketsError>>>` for `Result<(), MyError>`

aws_sdk_s3::types::SdkError<T>aws_sdk_ec2::Error に変換できません。ここでまず思うのは「サービスをまたいだ共通のエラー構造体はあるのかな?」ということです。実は aws_sdk_s3::types::SdkError<T>aws_smithy_http::result::SdkError<T> です。aws_sdk_ec2::types::SdkError<T> も同様です。

そのため以下のようにエラーを表現することができるはできます。ただ、これをやると MyError を利用する関数が全部 generic になって厳しいです。うまいことできる方法があったら知りたい……。

1
2
3
4
5
6
7
8
#[derive(thiserror::Error, Debug)]
enum MyError<T> {
#[error("some application error has been occurred.")]
AppHoge,

#[error("aws api error has been occurred")]
Aws(#[from] aws_sdk_ec2::types::SdkError<T>),
}

Aws のエラーを2つに分けてみる

次に考えたのはこのパターンです。利用する AWS のサービスが増えるたびに追加が必要だけど、そこまで追加することはないからまぁいいか、という感覚です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#[derive(thiserror::Error, Debug)]
enum MyError {
#[error("some application error has been occurred.")]
AppHoge,

#[error("aws api error has been occurred")]
AwsEc2(#[from] aws_sdk_ec2::Error),

#[error("aws api error has been occurred")]
AwsS3(#[from] aws_sdk_s3::Error),
}

impl<T> From<aws_sdk_ec2::types::SdkError<T>> for MyError
where
aws_sdk_ec2::types::SdkError<T>: Into<aws_sdk_ec2::Error>,
{
fn from(e: aws_sdk_ec2::types::SdkError<T>) -> Self {
MyError::Aws(e.into())
}
}

impl<T> From<aws_sdk_s3::types::SdkError<T>> for MyError
where
aws_sdk_s3::types::SdkError<T>: Into<aws_sdk_s3::Error>,
{
fn from(e: aws_sdk_s3::types::SdkError<T>) -> Self {
MyError::Aws(e.into())
}
}

// main は省略

ところがこれはダメです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
error[E0119]: conflicting implementations of trait `std::convert::From<aws_sdk_ec2::types::SdkError<_>>` for type `MyError`
--> src/main.rs:22:1
|
13 | / impl<T> From<aws_sdk_ec2::types::SdkError<T>> for MyError
14 | | where
15 | | aws_sdk_ec2::types::SdkError<T>: Into<aws_sdk_ec2::Error>,
16 | | {
... |
19 | | }
20 | | }
| |_- first implementation here
21 |
22 | / impl<T> From<aws_sdk_s3::types::SdkError<T>> for MyError
23 | | where
24 | | aws_sdk_s3::types::SdkError<T>: Into<aws_sdk_s3::Error>,
25 | | {
... |
28 | | }
29 | | }
| |_^ conflicting implementation for `MyError`

これがどうコンフリクトしているのかいまだに理解できていないです。aws_sdk_ec2::types::SdkError<T>aws_sdk_s3::types::SdkError<T> が同じものを指していることまではわかりますが、aws_sdk_ec2::Erroraws_sdk_s3::Error は別物なのでコンフリクトしなそうに思えてしまっています。

この状態から長いことガチャガチャいじってみたけどうまく解決しなかったので次の作戦に移行しました。

Box にぶち込む

これがひとまずの最終形です。AWS 系エラーは1つにまとめましたが、内部の型を残すのをあきらめました。これなら AWS のサービスが増えても何も変える必要がないです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#[derive(thiserror::Error, Debug)]
enum MyError {
#[error("some application error has been occurred.")]
AppHoge,

#[error("aws api error has been occurred")]
Aws(Box<dyn std::error::Error + Send + Sync + 'static>),
}

impl<T> From<aws_sdk_ec2::types::SdkError<T>> for MyError
where
T: std::error::Error + Send + Sync + 'static,
{
fn from(e: aws_sdk_ec2::types::SdkError<T>) -> Self {
MyError::Aws(Box::new(e))
}
}

// main は省略

もしもエラーから具体的なものが必要になったら以下のようにダウンキャストもできるのでよしとしておきましょう。

1
2
3
4
5
if let MyError::Aws(e) = error {
if let Some(e) = e.downcast_ref::<aws_sdk_ec2::types::SdkError<DescribeInstancesError>>() {
// do something
}
}