RustなWASMでweb-sysのgetElementByIdやquerySelectorの結果得られるElementから適当なHtmlInputElement等へ参照する型を変換する方法

## ECMAScript の場合

例として次のように `<input>` 要素が記述された HTML があった場合、

```html
<!-- .html -->
<input id="nyanko" />
```

上記に対して ECMAScript からは一般に `window.document` のメンバーの `getElementById` 関数や `querySelector` 関数または今回の例のように `id` に対してであればコードから見れば暗黙的に定義済みとなっている `window` の `.nyanko` メンバーを介して、

```js
// .js
let nyanko = document.getElementById('#nyanko')
alert( nyanko.value ) // 👈 undefined ではなく '' が出力される
```

のように要素を取得し、 `<input>` 要素に固有の `.value` メンバーを暗黙的に使用できる。 ECMAScript では "弱い動的型付け" によりプログラマーが理解していれば抽象型と具象型あるいは明示的な型変換などは気にせず簡潔な記述で目的の処理を記述しやすい。(もちろんその反面として、プログラマーがよく理解し注意深く記述しなければ不具合の温床となりうる切っ掛けに満ち溢れていたり、最適化がより難しいなどの課題も併せ持つ。)

## Rust な WASM で web-sys の場合

先に示した ECMAScript の例に対して、 Rust から WASM で web-sys を介して同様の処理を行いたい場合、同様のコードを記述しようとすると、 

```rust
// .rs
let w = web_sys::window().unwrap();
let d = w.document().unwrap();
let nyanko = d.get_element_by_id( "nyanko" ).unwrap();
w.alert_with_message( &nyanko.value() ).unwrap(); // 👈 翻訳時エラーとなる
```

となり、 `Element` 型には `.value` は未定義なので翻訳時エラーとなる。Rustは強い静的型付けにより言語特性として原則的にプログラマーに抽象型と具象型あるいは明示的な型変換を意図して記述させ安全性の担保を図っている。

`nyanko` 変数として束縛している対象は「抽象的に何かしらの`<????>`要素」を示す `Element` 型ではなく「具象的に`<input>`要素」である `HtmlInputElement` 型として参照可能な事を明示的に記述しなければ先の ECMAScript の例と同様の期待動作は得られない。

```rust
// .rs
use wasm_bindgen::JsCast; // ⇩で使う .dyn_ref はこの trait に含まれる
use web_sys::HtmlInputElement; // Cargo.toml で web_sys の features に追加した上で使用する

let w = web_sys::window().unwrap();
let d = w.document().unwrap();
let nyanko = d.get_element_by_id( "nyanko" ).unwrap();
let nyanko = nyanko.dyn_ref::<HtmlInputElement>().unwrap(); // 👈 型変換
w.alert_with_message( &nyanko.value() ).unwrap(); // 👈 '' が出力される
```

これで先の ECMAScript の例と同様に期待動作する。

### おまけ: `getElementById` しつつ `HtmlInputElement` など欲しい型を受け取る `get_element_by_id_as` 関数

前述の `dyn_ref` は型変換した参照を取得するため、次のような関数で lifetime を扱う積極的な理由が無ければ `dyn_into` で型変換する対象の所有権を持った状態で return するとよい。

```rust
fn get_element_by_id_as<T: JsCast>(id: &str) -> Option<T>
{
 let w = window()?;
 let d = w.document()?;
 let e = d.get_element_by_id(id)?;
 e.dyn_into::<T>().ok()
}
```

上記の関数は次のように使用できる:

```rust
let nyanko = get_element_by_id_as::<HtmlInputElement>("nyanko")?;

let w: Window = window()?;
w.alert_with_message( &nyanko.value() ).unwrap(); // 👈 '' が出力される
```

## 参考

- MDN
  - https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById
  - https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector
  - https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input
- web-sys
  - https://docs.rs/web-sys/latest/web_sys/struct.Document.html#method.get_element_by_id
  - https://docs.rs/web-sys/latest/web_sys/struct.Document.html#method.query_selector
  - https://docs.rs/web-sys/latest/web_sys/fn.window.html
  - https://docs.rs/web-sys/latest/web_sys/struct.Window.html
  - https://docs.rs/web-sys/latest/web_sys/struct.Document.html
  - https://docs.rs/web-sys/latest/web_sys/struct.Element.html
  - https://docs.rs/web-sys/latest/web_sys/struct.HtmlInputElement.html
- wasm-bindgen
  - https://docs.rs/wasm-bindgen/latest/wasm_bindgen/trait.JsCast.html

## License

- [CC0](https://creativecommons.org/choose/zero/)