☃️

【Rust Yew web-sys】初学向 Yewでinputへの入力内容取得、及び処理結果の表示

2023/12/11に公開

Yewweb-sysでのWeb開発

先人がJavaScriptの代替としてWebAssemblyに注目し始め、早数年の時が経つ。曰く、その高速な動作は、JavaScriptでは実現し得ないものであると。しかし初学の私がこれを試さんとするに、挫折を余儀なくされたことは幾度とあった。以為えらく、JavaScriptの簡便さの前に、WebAssemblyは到底及ばぬと。

WebAssemblyYew

私が挫折した原因の一は、手法の不定にあった。
WebAssemblyは直接記述することができる。
https://developer.mozilla.org/ja/docs/WebAssembly/Understanding_the_text_format
記述したものをWasm形式に変換する。
https://developer.mozilla.org/ja/docs/WebAssembly/Text_format_to_wasm

上記事より引用
wat2wasm simple.wat -o simple.wasm

一方、RustC等の言語から生成することもできる。
https://developer.mozilla.org/ja/docs/WebAssembly/Rust_to_Wasm

上記事より引用
wasm-pack build --target web

https://developer.mozilla.org/ja/docs/WebAssembly/C_to_Wasm

次の記事では、こうした手順を少しでも簡便にするべく、Makefileを用いている。
https://qiita.com/mokemokechicken/items/7768aaff465f2778e68c

そしてRustには、(誤解でなければ)WebAssemblyを自動的に生成する機能を備える、「Yew」がある。これにより、プログラムのcompileからWebAssemblyへの変換まで、全て次の一文で完結するようになるのである。

Yewとtrunkを用いた実行方法
trunk serve

ここまで論いた事柄から、Yewは簡易にWebAssemblyを利用するには不可欠であると感ぜられる。その上、Yewには公式のホームページがある。
https://yew.rs/ja/
日本語対応こそ不十分であるものの、Docs.rsに比して親切にも整理されている。初学の私は大いに頼ろうとした。

Yewの問題

Yewを試して私が感じたことを謇諤すれば、「断片的な情報ばかりで、結局如何にして形を成せばよいのかわからない」のである。

実行方法

先ずはTutorialと、そのプログラム及び実行方法を引用して紹介する。


1

https://yew.rs/ja/docs/getting-started/build-a-sample-app


プログラム
use wasm_bindgen::prelude::*;
use yew::prelude::*;

struct Model {
    link: ComponentLink<Self>,
    value: i64,
}

enum Msg {
    AddOne,
}

impl Component for Model {
    type Message = Msg;
    type Properties = ();
    fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
        Self {
            link,
            value: 0,
        }
    }

    fn update(&mut self, msg: Self::Message) -> ShouldRender {
        match msg {
            Msg::AddOne => self.value += 1
        }
        true
    }

    fn change(&mut self, _props: Self::Properties) -> ShouldRender {
        // Should only return "true" if new properties are different to
        // previously received properties.
        // This component has no properties so we will always return "false".
        false
    }

    fn view(&self) -> Html {
        html! {
            <div>
                <button onclick={self.link.callback(|_| Msg::AddOne)}>{ "+1" }</button>
                <p>{ self.value }</p>
            </div>
        }
    }
}

#[wasm_bindgen(start)]
pub fn run_app() {
    App::<Model>::new().mount_to_body();
}

実行方法
wasm-pack build --target web --out-name wasm --out-dir ./static
miniserve ./static --index index.html

2

https://yew.rs/ja/docs/tutorial


プログラム
use yew::prelude::*;

#[function_component(App)]
fn app() -> Html {
    html! {
        <h1>{ "Hello World" }</h1>
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}

実行方法
trunk serve --open

実行方法は言わずもがな、プログラムのfn main()内を見ても、二者が全く異なる手法で実行しようとしていることが分かるだろう。Yewは開発途上であるため、バージョンが異なると互換性がなくなっていることも珍しくない。そこで、両者のバージョンが記載されているCargo.tomlをも引用する。

  1. 1
    Cargo.toml
    [package]
    name = "yew-app"
    version = "0.1.0"
    authors = ["Yew App Developer <name@example.com>"]
    edition = "2018"
    
    [lib]
    crate-type = ["cdylib", "rlib"]
    
    [dependencies]
    yew = "0.17"
    wasm-bindgen = "0.2"
    
  2. 2
    Cargo.toml
    [package]
    name = "yew-app"
    version = "0.1.0"
    edition = "2021"
    
    [dependencies]
    yew = { git = "https://github.com/yewstack/yew/", features = ["csr"] }
    

上はyew = "0.17"とあるから、0.17であると分かる。尚、現在の最新は0.21であり、こちらの方は古い情報であることが分かる。
一方で下にはバージョンを示す数値がなく、直接GitHubを指している。

この違いは極めて重要である。にも関わらず、両者が混在しているため、私のような初学の混乱を招くのである。

爰に、本記事では例2の方式を採用することを断っておく。


開発手法

ここで一度、本記事の表題を確認しておく。
Yewinputへの入力内容取得、及び処理結果の表示」
具体的且つ基本的な課題は、次の三点である。

  1. Rustで、画面上の要素や、内容を取得できること。
  2. Rustで、画面上に何らかの内容を表示できること。
  3. Rustで、EventListnerを定義できること。

このようなWeb Applicationを作るからには、当然EventListnerを扱いたいものである。

EventListnerとは

EventListnerは、操作に連係させる関数である。一例に、JavaScriptでは次のように実装する。

index.html
<input type = "number" id = "inputArea" />
<button id = "button" >button</button>
<p id = "outputArea" ></p>

<script>
    /* buttonを押すとinputの内容を`<p id = "outputArea" ></p>`に表示する */
    document.getElementById("button") // `<button id = "button" >button</button>`を取得
    .addEventListner(
        "click", // clickに対応させる
        /* 以下の無名関数を連係する */
        () => {
            /* `<input type = "number" id = "inputArea" />`の値を取得する */
            const inputValue = document.getElementById("inputArea").value;
	
	    /* `<p id = "outputArea" ></p>`の内容を上書きする */
	    document.getElementById("outputArea").textContent = inputValue;
        }
    );
</script>

参考:
https://developer.mozilla.org/ja/docs/Web/API/Node/textContent

EventListnerを実装するにあたり、隘路となったのは親切な情報の少なさである。結論としては、次の情報を参看することで実装する。
https://yew-rs-api.web.app/next/yew/functional/fn.use_callback.html
プログラムを引用して示す。

Example
#[derive(Properties, PartialEq)]
pub struct Props {
    pub callback: Callback<String, String>,
}

#[function_component(MyComponent)]
fn my_component(props: &Props) -> Html {
    let greeting = props.callback.emit("Yew".to_string());

    html! {
        <>{ &greeting }</>
    }
}

#[function_component(UseCallback)]
fn callback() -> Html {
    let counter = use_state(|| 0);
    let onclick = {
        let counter = counter.clone();
        Callback::from(move |_| counter.set(*counter + 1))
    };

    // This callback depends on (), so it's created only once, then MyComponent
    // will be rendered only once even when you click the button multiple times.
    let callback = use_callback((), move |name, _| format!("Hello, {}!", name));

    // It can also be used for events, this callback depends on `counter`.
    let oncallback = use_callback(counter.clone(), move |_e, counter| {
        let _ = **counter;
    });

    html! {
        <div>
            <button {onclick}>{ "Increment value" }</button>
            <button onclick={oncallback}>{ "Callback" }</button>
            <p>
                <b>{ "Current value: " }</b>
                { *counter }
            </p>
            <MyComponent {callback} />
        </div>
    }
}

始めに私怨を込めて雑駁と指摘するのは、この例には関数の定義の仕方しか記述されておらず、「これをどのように実行するのか」には全く触れられていない点である。吾が私怨に反駁するとすれば、「逐一実行方法まで触れる必要はない」のだが、先述の通りYewの実行方法は唯一でないために、殊更不親切となっているのである。

本旨に戻ると、この例の中にはEventListnerの定義方法が二種類示されている。

  • Callback::from()
  • use_callback()

二者の差異はcounter.clone()の位置であり、使い勝手が異なる。
また、counteruse_state()によって定義されている。これをHTML中に埋め込むことで、counter.set()で値を変じると、HTML上でも値が変わるようになっている。

上の例は、このような考えに基づいて組まれたプログラムである。これにより、「画面上の値の更新」をすることは出来る。しかしながら、JavaScriptの手法と比して乖離が激しい。この例を見ただけでは、「画面上の要素の取得」を如何にすべきか、私には分からない。

JavaScriptweb-sys

本記事では、JavaScriptの代替は、これに近似しているweb-sysを利用する。当初、「YewでもJavaScript同様のことができる筈」と考えていた。しかし、以下の記事を拝見し、その考えは全く忘れてしまった。「これなら出来る」と確信したのである。
https://zenn.dev/kanal/articles/14a0c76bcc97d1
やはり、JavaScriptを経験している者ならば、JavaScriptと同じ手法をとる方が簡単である。そして未だ、Yewでどのように要素を取得するべきか理解していない。「できない」より「できる」を撰んだのである。

web-sysJavaScriptとの決定的な差異は、unwrap()を付する必要がある点である。次を比較せよ。

id="ID"である要素の取得(js)
document.getElementById("ID")
id="ID"である要素の取得(rs)
use web_sys::window;

window().unwrap().document().unwrap().get_element_by_id("ID").unwrap()
型の推移

Option<>の被覆を除去して中を取り出すのが、ここでのunwrap()の働きと言える。

camel caseとsnake case

camelとは駱駝、snakeとは蛇である。
camel caseとは、getElementByIdの如く、語の区切りを大文字にして示す記法である。
snake caseとは、get\_element\_by\_idの如く、語の区切りを_で示す記法である。
Rustでは、camel caseを使用すると「snake caseを使用せよ」と警告を受ける。

unwrap()とは、「失敗し得る処理」に付される関数であるが、ここでは、これが無いと実行できないから付しているに過ぎない。但し、処理が失敗していた場合には「プログラム全体が停止するため危険」である。停止されては困る場合、unwrap_or()等で、「失敗したらどう対処するか」を定義する必要がある。

その他、JavaScriptとは異なる点は数多あるが、不明なYewと比べると幾分ましと考えた次第である。

本題

今更だが、Rustは既に使用できる前提で話を進める。
今回作成するのは、

  1. 数値を入力する
  2. buttonを押すと、数値を取得し、計算する
  3. 計算結果を表示する

これらの簡単な例として、BMI計算ができるプログラムとする。

BMIとは

BMIとは:
https://www.e-healthnet.mhlw.go.jp/information/dictionary/metabolic/ym-002.html
BMI計算サイト:
https://keisan.casio.jp/exec/system/1161228732

BMIとはBody Mass Indexの略であり、「体格指数」と邦訳される。
体重をW[㎏]、身長をH[m]と置く時、

BMI = \frac{W}{H^2}
で計算される。
肥瘠判定の指標として世界的に用いられており、日本肥満学会は、18.5未満を低体重(やせ)、25以上を肥満、それ以外を普通体重としている。

Rust projectを作り、Yewweb-sysを追加する

project名はbmi\_calculatorとする。次を実行すると、基本要素が自動で生成される。

cargo new bmi_calculator

加えてindex.htmlを作成する。例として、内容は以下のようにする。

index.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>BMI</title>
    </head>
    <body>
    </body>
</html>

今、project内は次のような構成となった。

web-sysの追加

cargo add web-sys

初回ならば、これにより、自動的にCargo.lockが生成される。追加されたpackageが記されるが、基本意識する必要はない。
また、Cargo.tomlに次のような内容が追加される。今回のバージョンは0.3.66であった。

Cargo.toml(抜粋)
[dependencies]
web-sys = "0.3.66"

Yewの追加

Cargo.tomlに追記する。

Cargo.toml(抜粋)
[dependencies]
web-sys = "0.3.66"
yew = { git = "https://github.com/yewstack/yew/", features = ["csr"] }
featuresについて

先述した通り、本記事では作成したプログラムを次のように実行する。

fn main() {
    yew::Renderer::<App>::new().render();
}

このyew::Rendererについてのdocsを読むと、次の記述がある。

Available on crate feature csr only.

この記述にある通り、yew::Rendererを使用するには、csrfeatureを有効にする必要があり、これが抜けていると実行できない。この他にもこのような注意を要する場合があるため、適宜確認しなければならない。

Rustを記述する

完成は以下に示す。

main.rs
use yew::prelude::*;
use web_sys::{
    window,
    HtmlInputElement,
    wasm_bindgen::JsCast
};

/* clone()を使うために`PartialEq`を付与する。 */
#[derive(PartialEq)]
struct BMI {
    bmi_value: String,
} impl BMI {
    /* constructor */
    fn new_empty() -> Self {
        BMI {
            bmi_value: "".to_owned()
        }
    }
    fn new_value(value: f32) -> Self {
        let string_value = value.to_string();
        BMI {
            bmi_value: string_value
        }
    }
    /* 計算 */
    fn calculation(height: f32, weight: f32) -> f32 {
        /* 体重[㎏] ÷ {(身長[㎝] ÷ 100)[m]}² */
        weight / (height / 100.0).powi(2)
    }
}

/* idで探してvalueを得る */
fn fn_get_value_by_id(id: &str) -> String {
    window().unwrap()
    .document().unwrap()
    .get_element_by_id(id).unwrap()
    .dyn_ref::<HtmlInputElement>().unwrap()
    .value()
}

#[function_component(App)]
fn app() -> Html {
    /* HTMLに埋め込む変数 */
    let bmi_state = use_state(
        || {
            BMI::new_empty()
        }
    );

    /* Event Listner */
    let calculate_bmi = use_callback(
        bmi_state.clone(),
        |_mouse_event, clone_state| {
            let height = fn_get_value_by_id("height").parse::<f32>().unwrap();

            let weight = fn_get_value_by_id("weight").parse::<f32>().unwrap();

            let bmi = BMI::calculation(height, weight);

            clone_state.set(
                BMI::new_value(bmi)
            );
        }
    );

    html! {
        <>
            <h1>{"BMI calculation"}</h1>
            <p>{"height (㎝)"}<input type = "number" step = "0.1" id = "height" /></p>
            <p>{"weight (㎏)"}<input type = "number" step = "0.1" id = "weight" /></p>
            <p>{format!("BMI:{}", (*bmi_state).bmi_value)}</p>
            <p><button onclick = {calculate_bmi} >{"calculate"}</button></p>
        </>
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}

概要図
概説

概説

プログラムについての説明は以下に述べる。

main()

fn main() {
    yew::Renderer::<App>::new().render();
}

fnで関数を定義する。Rustでは、main()がプログラムの開始点となる。

use

use yew::prelude::*;
use web_sys::{
    window,
    HtmlInputElement,
    wasm_bindgen::JsCast
};

使用するcrateを明記している。既に機能が整理され、提供されているプログラム群を、Rustではcrateと呼ぶ。即ち、Yewweb-sysも、crateである。
use yew::prelude::*;は、Tutorialの例に倣っているに過ぎない。web_sysは、VSCodeの拡張機能に任せ、自動的に追加したものである。

struct BMI

/* clone()を使うために`PartialEq`を付与する。 */
#[derive(PartialEq)]
struct BMI {
    bmi_value: String,
} impl BMI {
    /* constructor */
    fn new_empty() -> Self {
        BMI {
            bmi_value: "".to_string()
        }
    }
    fn new_value(value: f32) -> Self {
        let string_value = value.to_string();
        BMI {
            bmi_value: string_value
        }
    }
    /* 計算 */
    fn calculation(height: f32, weight: f32) -> f32 {
        /* 体重[㎏] ÷ {(身長[㎝] ÷ 100)[m]}² */
        weight / (height / 100.0).powi(2)
    }
}

structは構造体と呼ばれる。ここでは、「オブジェクト指向」に於けるclassに似た内容を記述している。

構造体 BMI

struct BMI {
    bmi_value: String,
}

この構造体は、String型の文字列をbmi_valueという名で保有する。bmi_valueは関数で扱うため、&str型では成立しない。

文字列の型

Rustには、文字列の型として&strStringの二種がある。
簡単には、次のように分別する。

概要
&str そのまま使う場合
String 内容が変わる場合
関数から返却する場合

&strは、厳格で融通が利かないStringは、&strではできないこと(関数処理など)ができる。この選択を誤ると、プログラムが実行できなくなる。
この分類や、strでなく&strであることは、一見して受け入れ難いものである。しかしここでは受容することとし、敢えてその理由は述べない。

Rustの構造体や「オブジェクト指向」のclassは、それ自体が「型」を成す。今後、BMIは「型」として扱うことができる。

構造体に関数を実装する

impl BMI {
    /* constructor */
    fn new_empty() -> Self {
        BMI {
            bmi_value: "".to_string()
        }
    }
    fn new_value(value: f32) -> Self {
        let clone_value = value.clone();
        let string_value = clone_value.to_string();
        BMI {
            bmi_value: string_value
        }
    }
    /* 計算 */
    fn calculation(height: f32, weight: f32) -> f32 {
        /* 体重[㎏] ÷ {(身長[㎝] ÷ 100)[m]}² */
        weight / (height / 100.0).powi(2)
    }
}

new_empty()

fn new_empty() -> Self {
    BMI {
        bmi_value: "".to_string()
    }
}

bmi_valueが空の文字列を保有する、BMI型構造体を返却する。
関数が返却する値の型は->で明記する。ここでは、SelfBMIを言う。
空の文字列""&str型であるため、to_string()String型に変換する。

new_value()

fn new_value(value: f32) -> Self {
    let string_value = value.to_string();
    BMI {
        bmi_value: string_value
    }
}

bmi_valueが、小数を表す文字列を保有する、BMI型構造体を返却する。
value: f32は、この関数がf32型小数値を受け、valueという名で扱うことを示す。
value.to_string()は、小数値をString型文字列に変換している。(例:1.1という小数値が"1.1"という文字列となる)

式と戻り値との区別

関数の中で、処理を表す式は末尾に;が付く。反対に;が付かない場合は戻り値を表し、この値が返却される。

{
    let string_value = value.to_string();
    BMI { bmi_value: string_value }
}

let string_value = value.to_string();は処理である。BMI { bmi_value: string_value }は戻り値である。
別言語ではreturnと書かなければ返却されないことがしばしばだが、それに比べて意図が分かりづらく、誤りやすい。

calculation()

fn calculation(height: f32, weight: f32) -> f32 {
    /* 体重[㎏] ÷ {(身長[㎝] ÷ 100)[m]}² */
    weight / (height / 100.0).powi(2)
}

身長と体重を受け、BMIを計算する。
powi()は、整数指数の累乗である。

構造体に属性を付与する

#[derive(PartialEq)]

後のbmi_state.clone()に対し、VSCodeの拡張機能が自動補完したものである。

fn_get_value_by_id

fn fn_get_value_by_id(id: &str) -> String {
    window().unwrap()
    .document().unwrap()
    .get_element_by_id(id).unwrap()
    .dyn_ref::<HtmlInputElement>().unwrap()
    .value()
}

局所的な利便性を上げるため、IDからvalueを取得する一連の処理を関数とした。

単にwindow().unwrap().document().unwrap().get_element_by_id(id).unwrap().value()としたのではerrorになるため、型変換を経由する。
参考記事:
https://qiita.com/usagi/items/40524df09959d9584d48

app()

#[function_component(App)]
fn app() -> Html {
    /* HTMLに埋め込む変数 */
    let bmi_state = use_state(
        || {
            BMI::new_empty()
        }
    );

    /* Event Listner */
    let calculate_bmi = use_callback(
        bmi_state.clone(),
        |_mouse_event, clone_state| {
            let height = fn_get_value_by_id("height").parse::<f32>().unwrap();

            let weight = fn_get_value_by_id("weight").parse::<f32>().unwrap();

            let bmi = BMI::calculation(height, weight);

            clone_state.set(
                BMI::new_value(bmi)
            );
        }
    );

    html! {
        <>
            <h1>{"BMI calculation"}</h1>
            <p>{"height (㎝)"}<input type = "number" step = "0.1" id = "height" /></p>
            <p>{"weight (㎏)"}<input type = "number" step = "0.1" id = "weight" /></p>
            <p>{format!("BMI:{}", (*bmi_state).bmi_value)}</p>
            <p><button onclick = {calculate_bmi} >{"calculate"}</button></p>
        </>
    }
}

main()によって実行される。

bmi_state

let bmi_state = use_state(
    || {
        BMI::new_empty()
    }
);

BMI::new_empty()が返却した、空の文字列""を持つBMI型構造体を保有する。

名前解決

::は名前解決のための記号である。new_empty()が何処から来たのかを、BMI::と付することで明記する。

無名関数

|| BMI::new_empty()は、無名関数を表す。||は引数を表す。ここでは、引数はないことを示す。そのあとに続く記述は、関数としての処理を示す。次に同じ。

fn no_name() -> BMI {
    BMI::new_empty()
}

calculate_bmi

let calculate_bmi = use_callback(
    bmi_state.clone(),
    |_mouse_event, clone_state| {
        let height = fn_get_value_by_id("height").parse::<f32>().unwrap();

        let weight = fn_get_value_by_id("weight").parse::<f32>().unwrap();

        let bmi = BMI::calculation(height, weight);

        clone_state.set(
            BMI::new_value(bmi)
        );
    }
);

use_callback()は、EventListnerを定める。
第一引数には、bmi_statecloneを指定する。
第二引数には、関数を指定する。

無名関数

|_mouse_event, clone_state| {
    let height = fn_get_value_by_id("height").parse::<f32>().unwrap();

    let weight = fn_get_value_by_id("weight").parse::<f32>().unwrap();

    let bmi = BMI::calculation(height, weight);

    clone_state.set(
        BMI::new_value(bmi)
    );
}

引数にはMouseEvent型の_mouse_eventと、&UseStateHandle<BMI>型のclone_stateを定める。

使用しない変数

変数名を_から始めることで、その変数を使用していなくともerrorにならない。

身長と体重は次のように取得し、変換する。

let bmi = BMI::calculation(height, weight);では、BMIの計算結果をbmiで受け取る。

clone_state.set(BMI::new_value(bmi));では、1. BMI::new_value()bmiを渡し、bmiの値を持つBMI型構造体を受け取る。2. 受け取った構造体で、clone_stateを更新する。

html!

html! {
    <>
        <h1>{"BMI calculation"}</h1>
        <p>{"height (㎝)"}<input type = "number" step = "0.1" id = "height" /></p>
        <p>{"weight (㎏)"}<input type = "number" step = "0.1" id = "weight" /></p>
        <p>{format!("BMI:{}", (*bmi_state).bmi_value)}</p>
        <p><button onclick = {calculate_bmi} >{"calculate"}</button></p>
    </>
}

HTMLを生成し、app()の戻り値として返却する。

参照外し

(*bmi_state).bmi_valueの型推移は次の通りである。

*は参照外し演算子である。UseStateHandle<BMI>からBMIを抜き出すために使用している。この他、次のような対応関係もある。

&は参照、*が参照外しである。本記事では敢えてこれ以上述べない。

format!

ここでは、"BMI:{}"{}に、(*bmi_state).bmi_valueを当て嵌めた文字列を生成している。

Yewhtml!macroでは、<h1>{"BMI calculation"}</h1>のように、{}HTML要素内の内容を記述したり、<button onclick = {calculate_bmi} >のように、EventListnerを指定したりする。(Webブラウザーから、onclick = {calculate_bmi}を直接見ることはできない)

また、html!macroには、必ず一つのまとまりとしてHTMLを指定する。html!{<p></p><div></div>}のような指定はできないため、空の<></>で一括している。

!とmacro

Rustでは、!の付くものをmacroと呼ぶ。その他、これまでに出現していた#[]macroである。関数とは異なり、macroには、compile時に「置換される」という特性があるが、ここでは敢えて述べない。

#[function_component(App)]

この属性を付与された関数は、戻り値としてHtml型の値を返却する必要がある。また先述の通り、擬似的にHTML要素として使用できるようになる。

実行する

諸説ある実行方法だが、本記事では次の通り行う。

準備

実行にあたり、trunkを使用する。

cargo install --locked trunk

また私自身記憶が曖昧だが、wasm32-unknown-unknownを追加する必要があると思われる。

rustup target add wasm32-unknown-unknown

その他若し不足があれば、指南に従う外ない。
https://yew.rs/ja/docs/getting-started/introduction

実行

先にも示したが、以下のようにして実行する。

trunk serve --open

--openを付すると、自動的にWebブラウザーが開かれ、127.0.0.1:8080を表示する。

作成された頁
200\%拡大画面

BMI計算例
計算例

小数点以下の処理こそしていないが、入力を基に計算できていることが確かめられた。

私がRustを知り、Yewを知り、試し始めたのは丁度一年前であった。この一年間、Yewの事を考えていた時間はさして多くないとはいえ、一年前の素朴な疑問「JavaScriptと同じことをするにはどうすればよいのか」を解決するまで一年が過ぎた事實には、淒涼の感を覚える。しかし、こうして攀籬の一端を整理できたことには満足している。
Yewは、Web Application開発のみならず、TauriによるDesktop Application開発にも応用が期待される。Yewを試す人が増え、より易しい情報が増えることをここに願う。

Discussion