dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

2021年1〜3月のふりかえり

完全に出遅れたが今年も3ヶ月おきにふりかえりをしていこうと思う。

やったこと

1月:「Go 言語でつくるインタプリタ入門」を途中まで Rust で頑張るものの挫折した

Go言語でつくるインタプリタ

Go言語でつくるインタプリタ

  • 作者:Thorsten Ball
  • 発売日: 2018/06/16
  • メディア: 単行本(ソフトカバー)

Rust の勉強のため & インタプリタについて知りたいというモチベーションで昨年12月ぐらいから取り組んでたものの、途中で飽きてしまい、こんなモチベーションで続けるぐらいなら気になってる Let's build a browser engine やった方がいいなーと思ってやめた。

たぶん、本格的に面白くなる前にやめてしまったというか、書かれたプログラムを字句解析器(レキサー)→構文解析器(パーサー)という手順でもって抽象構文木(AST)にするというあたりはなんとなく知っており、新しい知識が得られるとしたらこのあとそれを評価する部分のしくみだと思うので、そこまでたどり着けなかったのはもったいない気もする。

あとは、年始にこれの特集記事「Web ページが表示されるまで」を読んだ。
感想をブログにアウトプットするところまでできてなかった。


2月:「Let's build a browser engine!」をやった

というわけで自分の欲求に忠実に生きようと2月からやったのがこの連載記事。

内容・感想はここに書いたが、ブラウザのレンダリングエンジン(の超簡易的なやつ)を Rust で書いてみよう、というもの。
「Let's build a browser engine!」を読んでRustで簡易レンダリングエンジンを作った - dackdive's blog

これはなかなか面白かった。
レンダリングエンジンのおおざっぱな仕組みは理解していたが、実際書くとここまでシンプルでもレイアウトツリーの構築とかはこんなにめんどいのかー、みたいな感想を持った。


3月:なんか色々手を出した & 「ゼロからのOS自作入門」を始めた

3月の上旬は Software Design 3月号の WebAssembly の特集記事を読んだ。

メモはこちら:Software Design 2021年3月号の「WebAssembly入門」を読んだ - dackdive's blog

そして下旬にこちらの本が発売されたので、今はこれを読んでいる(通称「みかん本」)。

ゼロからのOS自作入門

ゼロからのOS自作入門

OS を作ることを通じて低レイヤのこともっと理解したい〜と思ってたのでとても良いタイミングだった。

また、みかん本を読みつつスキマ時間に Rust の勉強を続けたくて Exercism をやっている。

よくあるプログラミング学習サービスなんだけど、メンターがいてこちらが提出したコードに対してアドバイスとか Tips を教えてくれるのが良い。


1月〜現在:副業で Salesforce 開発をはじめた

Scalebase というサブスクリプションの契約管理 SaaS を開発しているアルプ株式会社で1月からお世話になっている。
Scalebase には Salesforce と連携する Scalebase Connect というサービスも展開しており、そこの機能追加や改善タスクを業務委託で行っている。

Salesforce 開発にがっつり関わるのは久しぶりなのでまた勉強し直してる。Lightning Web Component という Salesforce 独自のフレームワーク(※)とか。
フレームワークというと語弊があるかもしれない(参考)。でも React とか Vue みたいなものだと個人的には思ってる

副業先は(オンラインだけど)いつもワイワイやっていてとても雰囲気がよく、自分も気持ちよく仕事させてもらっている。

所感

集中力・モチベーションが上がらないと感じる時期があった

これは自分でも原因がよくわからない、かつ今も復活したかと言われると怪しいんだけど
2月頃からなんとなくしんどいと感じることが多かった。
具体的には、なんとなく朝働くぞという気持ちに切り替えられなかったり、あんまり大した成果出せてないなと感じたり、その割に仕事終わりに謎の疲れがあったり、などなど。

色々考えたけど結局なんでなのかはよくわからず。ただ自分が今こういう状況であることが自覚できたことはよかった。
在宅勤務にも慣れて、なんなら家の環境整えたし通勤なくなったしリモート快適じゃん!ぐらいに思ってたけど、そうでもないんだなと。

それから、こういった状況にあることを社内で何人かと話す機会があって、それでずいぶん楽になったこともあったので、当たり前だけど人と話すのって大事だなあと。
自分はうまくいってないときとか気分が乗らないときに人とのコミュニケーションを避ける傾向にある気がするので、このへんは気をつけたい。

大学進学について

コンピューターサイエンスの学位を取るために大学に行きたいという欲は依然としてある。
今までは University Of People(UoPeople)しか可能性がないと思っていたが、放送大学という選択肢もあることがわかった。

2月にこちらの記事を読んで、大変参考になった。

今の気持ちとしては

  • 「いつか金銭的・時間的に余裕ができたら行きたい」だとそんなタイミングは一生来ないので、動き出したほうがいいと思う
  • 一方で、大学に行く目的はもう少し整理したい
    • 体系だったカリキュラムで学べること、学んだことの証明が得られることが主な理由だが、具体的に何を学びたいのか?
  • 英語学習の必要性も感じているので、自分には UoPeople が良いと思ってる

という感じ。それもあって先月末に TOEFL を受験したりしてた。
TOEFL iBT Home Editionを受験した - dackdive's blog

UoPeople のカリキュラムや単位移行、入学手続きの方法などをきちんと調べるのが次のアクションになりそう。

次の四半期(2021年4〜6月)のテーマ

  • 「ゼロからのOS自作入門」を読む!
    • 当面はこれかなー。1,2ヶ月かもっとかかる気がしている...
  • Exercism 継続する
  • UoPeople への入学について必要なこと調べ終える
  • みかん本が終わったら「Rustで始めるTCP自作入門:ひつじ技研」とか読みたいな

TOEFL iBT Home Editionを受験した

タイトルの通り、TOEFL iBT が自宅で受験できる Home Edition というものを最近受験した。
TOEFL 自体も初めてだったので、Home Edition 受験の様子や TOEFL iBT 自体についてこんな感じでしたというのをメモしておく。

TOEFL iBT Home Edition について

より引用。

TOEFL®テスト主催団体ETSでは、新型コロナウイルス感染防止の対応策として、自宅受験「TOEFL iBT® Home Edition」を提供しています。 TOEFL iBT® Home Editionは、通常の TOEFL iBT®テストと同じ内容、フォーマット、画面のテストをProctorU®の試験監督者によるオンライン監視の下、自分のパソコンを使用して自宅で受験します。週に4日24時間受験でき、最短で申込完了の翌日に受験することが可能です。

通常テストセンターで受験する TOEFL と同じ内容のテストが自宅で受験できるというもの。
お手軽に受験できて便利、と思って勢いで申し込んだんだけど、実際に受験環境の条件などチェックするとけっこう厳しい制約があるなーと感じた。

具体的には、自分が印象に残ってるところだと以下のような点がある。

  • 以下 2 つを PC にインストールしないといけない
  • 受験環境の制約がきつい
    • ヘッドセットやイヤホンは使用不可
      • なので周囲への音漏れが気になる場合は厳しい
    • メモを取ることは認められているが、通常のノートは不可でホワイトボード or 透明なシートプロテクターに入れた紙のみ OK
    • 机周りだけでなく、部屋全体見渡して疑われそうなものは撤去しておく必要がある
    • 試験開始時に試験監督者に向けて、部屋全体をカメラで映し何もないことを証明する必要がある
    • ※モニターはOKかどうかわからなかった。自分は使わなかったら使わなかったで画面をなにかで覆うよう指示された

詳しくは

を一読することをオススメ。

また、試験開始時の流れは

  • ETS Secure Test Browser を立ち上げておく
  • Chrome 拡張をインストールした Chrome または Firefox で、試験用 URL にアクセスする
  • ProctorU 経由で試験監督者がログインしてくる(テレビ会議みたいな感じ)
  • そのまま指示に従い、身分証明書の提示や部屋の様子をカメラ越しにチェックする
  • 試験監督者が自分のPCを遠隔操作して、ETS Secure Test Browser にログイン名・パスワードを入力しログインする
  • 試験開始

こんな感じ。
試験監督者は、自分の場合は海外の方だったので指示は全部英語。

試験中はずっと試験監督者の監視があるが、何もなければ試験監督者からは何も言われないので試験に集中できる。
(自分は一度チャットで「顔が見えなくなったから確認させて」って言われた)

TOEFL iBT の試験内容・出題形式について

日本語だとこのページに概要がまとまっている。

Reading, Listening, Speaking, Writing の4つのセクションに分かれており、各30点満点、合計120点満点。
テスト時間は約3時間とあるが、Home Edition の場合最初のセットアップやガイダンスがあるので3時間半ぐらいは見込んだ方がいい。

また、

というページの「詳しく見る」のリンク先で、セクションごとの詳しい出題形式が動画で説明されているので、ざっとでも一度目を通した方がいい。

ページと動画は残念ながら英語のみ。動画1本あたり5分前後だけど、だいたい出題形式の説明は前半2,3分で終わって後半はサンプル問題や模範解答になってるので、そこまで時間はかからない。

あるいは、自分は気づくのが遅れて受けられなかったが、無料模擬テストもある。

結果と感想

f:id:dackdive:20210407174506p:plain

3/29に受験してスコアが公開されたのが4/7。

わかってはいたけど Speaking が他と比べて悪い。 Performance Descriptors for the TOEFL iBT® Test によれば Speaking でこの点は Low-Intermediate(5段階で真ん中)なのでやはり力を入れないといけない部分なんだろう。
試験の手応えとしても全然話せてる感じがしなかった。

この点数がどれくらいのレベルなのかわからないが、目安にしていた University of People(海外のオンライン大学)の英語要件によれば 71 点なのでそこはクリア。

English Proficiency | University of the People

ちなみにジョージア工科大学だと 90 点以上が必要。厳しい。

English Proficiency Requirements | Grad Studies | Georgia Institute of Technology | Atlanta, GA

感想としては、とにかくめちゃくちゃ疲れた。
TOEIC と同じ感覚で臨んで、しかも 21時スタートとかにしてたので集中力が保たなかった。
次回受けるとしたら土日か平日半休とって受けたい。

あとテスト内容としてもTOEICと比べてはるかに難しい。
Reading, Listening は感覚としては TOEIC の最後の方の難しいやつだけ出される感じ。
90点には8点足らないけど、正直これよりスコア伸ばせる気はしない。

ともあれまた英語学習再開しようかな(しないとヤバいな)という気持ちになれたので良し。

Software Design 2021年3月号の「WebAssembly入門」を読んだ

rust-jp の Slack で知り、読んでみました。
3 ~ 4 時間でさらっと読めるボリューム。

全体的に手を動かす成分はそこまで多くなかった印象で、読み物として面白かった。
以下、各章の感想など。

第1章 WebAssemblyの登場の経緯と利用方法

自分のこれまでの知識を復習しつつ、歴史的経緯などは知らなかった部分も多かった。
WAT のフォーマットについては以前読んだこちらの記事が良かった。

WebAssemblyハンズオン: 実際に動かして基礎を学ぶ(翻訳)|TechRacho(テックラッチョ)〜エンジニアの「?」を「!」に〜|BPS株式会社

また、印象的だったのはWasmの用途のところで

現代のブラウザが JavaScript を高速に実行するためには、大まかに「(最適化をあまりしないので)早く実行開始できるけど実行速度は遅い処理系」や「実行速度は速いけど(最適化に時間がかかるので)実行開始までが遅い処理系」といった、複数の方式の処理系を使い分けることが多いです。その関係で実行速度を制御しづらく、ブレが生じやすいのです。一方で Wasm はそうした実行時の最適化をあまり伴わない分、処理速度が比較的安定していることも、リアルタイム処理に採用される一因なのではないかと推測されます。

という記述があったが、Wasm は処理速度が安定しているという話は以前 Front-End Study でも聞いたなあ。

www.youtube.com

f:id:dackdive:20210324001919p:plain

(動画 1:14:00 あたりから)

第2章 Wasmで広がる Webアプリの可能性

元々多言語で作られたものを Wasm を通して Web アプリ上で動かす、の例として FFmpegImageMagick のデモが紹介されていた。
コードもあってわかりやすい。

第3章 WASIとProxy-Wasm

Wasm をブラウザ外で使うために定められた仕様の話だったり、ブラウザ外 Wasm の一例として Proxy-Wasm やそのリファレンス実装である Envoy の話など。

Proxy-Wasm 気になってたので概要だけでも理解できてよかった。

ちょうど先週末の Kernel/VM 探検隊 というイベントでも関連セッションがあったみたいで、スライドも見た。

www.slideshare.net

あと記事の著者は昨年末の WebAssembly night #10 - connpass でも Proxy-Wasm について登壇されてた。(観てなかったの思い出した)

メモ

読みながらとったメモ。

## 第1章

- Wasmの起源は asm.js (2013年3月 Mozilla)
    - JSには任意のオブジェクトに対しビット単位演算子OR `|` を適用すると強制的に整数に変換するという特性がある
    - これを生かして、 `foo | 0` は整数型の式とみなす、などのルールを設けることで、JSを静的型付け言語となるかのように制限する
    - このような前提を設けることで、Cなどのプログラミングからasm.jsに変換する

- asm.js はJSと高い互換性があるので多くのブラウザでサポートされた一方、プリミティブな命令群を生成する関係上コード量が増える、増えると構文解析に時間がかかるといった問題があった
- Wasmはasm.jsの問題を解決
    - バイトコードでサイズを小さく構文解析が簡単に
    - プラス、より安全にするためにJSの環境から分離させる
- WAT
    - `wat2wasm` WAT → Wasm 変換ツール。WebAssembly Binary Toolkit に含まれる
    - S式: `(宣言 .... 宣言の内容....)`
- WasmはJSを置き換えるか→NO
    - 現代の多くのプログラミング言語は重厚なランタイムが必要で、それをWasmにコンパイルするとサイズが大きくなる
    - Wasm からブラウザの API を直接呼ぶことはできなずJSのAPI経由で間接的に呼ぶことになる。ので Wasm ←→ JS 間の呼び出しオーバーヘッドがある
    - Wasm の公式見解としても、Wasmがコアのロジック担当、JSがそれとHTML/CSSベースのGUIとの橋渡し役になる、という役割分担を想定して仕様を策定しているらしい
- Wasm は速度が安定しているみたいな話
    - JSは「(最適化をあまりしないので)早く実行開始できるけど実行速度は遅い処理系」や「実行速度は速いけど(最適化に時間がかかるので)実行開始までが遅い処理系」といった、複数の方式の処理系を使い分けることが多い。その関係で実行速度を制御しづらくブレが生じやすい
    - Wasm meetup で聞いたかも
- Wasm 活用事例
    - Unity は 2018.1 で Wasm 対応
    - C言語からの移植
        - FFmpeg
        - md4c というmarkdown parser

### Wasm に触れる

- wasm-pack: Rust で作成したWasmのモジュールをフロントエンドやNode.jsなどのプロジェクトで使いやすいようビルドしてくれるツール
    - コンパイルしたWasmモジュールやwasm-bindgenが生成したラッパーをnpmパッケージとして使えるようにしてくれる
- wasm-bindgen
    - Wasm で定義した関数は通常整数や浮動小数点数といった単純な値しかやり取りできない
    - 文字列や構造体、配列といった値を扱うには「線形メモリ」と呼ばれるWasmモジュール専用の領域に書き込む必要がある
    - そのへんの面倒を担ってくれるラッパーを生成する
- "`crate-type = ["cdylib"]` は、このプロジェクトが動的リンクされるライブラリとしてビルドすることを宣言している"
    - 🤔
- `wasm-opt` (wasm-packが内部で使ってる)
    - [https://github.com/rustwasm/wasm-pack/issues/886](https://github.com/rustwasm/wasm-pack/issues/886) は wasm-bindgen v0.2.70 で fixed
        - "エクスポートする関数が文字列を返す関数となっていた場合、これが必要ですのでご注意ください。"

## 第3章 WASIとProxy-Wasm

- なぜブラウザ上でWasmが動くの?
    - Wasm そのものはスタックベースのVMおよびそこで実行されるバイナリの仕様で、ブラウザとは関係ない
    - ブラウザがWasmのVMを実装しているので動かせる、というわけ
    - Wasmの実行環境のことを「ホスト環境」と呼ぶ
- WasmはどうやってJSやブラウザと連携してるの?
    - Wasm の仕様にある Import section, Export section, Host function がカギ
    - Import section
        - Wasm のバイナリの一部
        - そのプログラムに対して外部から提供されるリソースを記述したもの
        - 例:グローバル変数や関数
        - import 時にJS側で渡すやつかな
    - Export section
        - 同じくバイナリの一部
        - プログラムの中でほかのWasmのバイナリやホスト環境で利用可能なものを記述したもの
        - `#[wasm_bindgen]` つけた関数とかかな
    - Host function
        - ホスト環境側で定義した関数を Wasm にリンクした際のオブジェクトのこと

    ### WASI

    - 複数のホスト環境それぞれに共通する、システムコールに対応するHost functionの仕様を策定したもの
        - 例:Wasmのプログラムからファイルを読み書きしたり、現在時刻を取得するためのHost function
    - WASI ランタイム:WASIを実装した、Wasmを実行可能なホスト環境のプログラム
        - Lucet, Wasmtime, Wasmer, WAVM, SSVM など
    - Wasm の応用例:Krustlet
        - Kubernetes上でコンテナとしてWasmのバイナリを実行する環境を提供するプロジェクト
        - "通常のコンテナを利用する場合と比べ、WASI を利用してユーザーのプログラムを実行した場合、システムコールはプロセスが走っている OS カーネルではなく WASI ランタイムのレイヤに発行されるため、プロセスとしての分離性が高くよりセキュアであると考えることができます"

### Proxy-Wasm

- 背景となるEnvoy Proxy
    - オープンソースのプロキシサーバ
    - Service Mesh(ざっくり言うと、マイクロサービスのネットワーク通信をプロキシ経由にする)のData Plane(そこで使われるプロキシ)
- Envoyはプロキシサーバなので個別ユースケースが無数に存在する
    - そのためプラグイン機構が存在するが、以下のような課題もあった
        - C++ で開発しなければ いけない」「Private なユースケースの場合は、 Envoy の Upstream へマージすることはできない ので、ユーザーが独自ビルド&メンテナンスを しなければいけない」「拡張機能追加後にすべて の Envoy のプロセスを入れ替えなければならな い」
    - "「複数言語をサポート」「Envoy 自体の独自ビ ルドは必要ない」「Envoy のプロセスを止めるこ となく動的にプラグインを Load 可能」「堅牢な セキュリティ」などの要求を満たした拡張機構の 開発に乗り出しました。"
- Proxy-Wasm
    - Wasm のプログラムによりプロキシを拡張するための VM とホスト環境の間の ABI の仕様策定やSDK開発を目的としたプロジェクト
    - WASIと独立しているが、実質的にWASIの一部APIと各種プログラミング言語のWASI向けのツールチェインに依存しているため、WASIのサブプロジェクトになるのではという話もある
- Proxy-Wasm の仕様と実装
    - 双方向的なのが特徴。WASIはWasmの中からホスト環境へ発行するHost functionの仕様なので一方向
    - 例:VM→ホスト環境
        - `proxy_send_http_response`
    - 例:ホスト環境→VM
        - `proxy_on_request_headers`
- Proxy-Wasm の課題
    - ホスト環境の実装の難しさ
        - Wasm のプログラムによってホスト環境がクラッシュしないようにしないといけない
        - 悪意のある Wasm バイナリがロードされるかもしれない
    - パフォーマンス:50%ほどネイティブと比較して遅い
    - VMの実装の選び方

「Let's build a browser engine!」を読んでRustで簡易レンダリングエンジンを作った

Rust を勉強したらやってみたいなと思ってた記事。

記事について

タイトルの通り、簡単なブラウザのレンダリングエンジンを Rust で作る、という趣旨の記事。
著者は Servo という Mozilla が開発しているレンダリングエンジンのチームに所属(していたらしい。現在は不明)。

最終的にできるもの

たとえば、以下のような HTML と CSS

<html>
  <head>
    <title>Test</title>
  </head>
  <p class="inner">
    Hello, <span id="name">world!</span>
  </p>
  <p class="inner" id="bye">
    Goodbye!
  </p>
</html>
* {
  display: block;
}

span {
  display: inline;
}

html {
  width: 600px;
  padding: 10px;
  border-width: 1px;
  margin: auto;
  background: #ffffff;
}

head {
  display: none;
}

.outer {
  background: #00ccff;
  border-color: #666666;
  border-width: 2px;
  margin: 50px;
  padding: 50px;
}

.inner {
  border-color: #cc0000;
  border-width: 4px;
  height: 100px;
  margin-bottom: 20px;
  width: 500px;
}

.inner#bye {
  background: #ffff00;
}

span#name {
  background: red;
  color: white;
}

をパースし、↓ のような画像を出力できるようになる。

f:id:dackdive:20210217183954p:plain:w320

見てわかるように、テキストの描画には対応していない。

また最終的に画像を出力する部分はライブラリ(image クレート)を使用しておりスコープ外。それと、実際のブラウザウィンドウができるわけではない。

やってみた感想

レンダリングエンジンのしくみについては以前 Google

を読んでいたので、全体的な流れについては理解していたが、このときの記憶をおさらいする感じで読めたのでよかった。
また、特に後半の Style tree から Layout tree を構築して最終的にピクセル単位の情報(どこの座標は何色で描画するか)にするまでの処理はこちらの方が詳しく書かれており、勉強になった。

あと、この記事は昨年の秋頃に読もうとして Rust 全然わからなくてすぐに挫折したんだけど、今だと書いてあることは読めるぐらいにはなっていて数ヶ月前よりは成長を実感できた。
相変わらず所有権まわりというか、どういうときに参照のままで計算してよくてどういうときにムーブしたらいいのか、とか、ここで参照外しする必要あるの?とかはよくわかってない。

ソースコードはここに置いてある。

github.com

元のコードと比べて、2018 edition で一部変更が必要だったところ、image クレートの最新バージョンでうまく動かないところなどは修正してある。
またテストが全くなかったので Part 4 ぐらいまではテストを追加しており、参考になるかもしれない。

学び

レンダリングの流れ

記事中の画像を引用する。

f:id:dackdive:20210223100557p:plain

大きな流れは

  • 最初に HTML と CSS をそれぞれパースし DOM と CSS の Rule 群を構築する
  • DOM と Rules から Style tree(DOM にスタイル情報が付与されたような木構造)を構築する
  • Style tree から Layout tree(要素がブロック要素かインライン要素か...という情報と、コンテンツの矩形領域の大きさ)を構築する
  • Layout tree から描画コマンドのリストを作成し、描画

となっている。

Part2: HTML のパース

f:id:dackdive:20210223095357p:plain

特に言うことなし。HTML 文字列をパースして DOM を構築する。
記事中のパーサーは文字列をいきなり最終的な DOM に変換しており Lexer(文字列を Token に変換するやつ)は登場しなかった。

Part3: CSS のパース

f:id:dackdive:20210223095410p:plain

DOM と異なり、こちらは木構造ではなくただのリストになっている(型で書くと Vec<Rule>)。
記事中のパーサーは simple selector のみサポートしており、複数のセレクターを >+ などの結合子でつなげたものはサポートしていない。
ref. 結合子 - ウェブ開発を学ぶ | MDN

Part4: Style tree

f:id:dackdive:20210223102259p:plain

Style tree は DOM の各要素にスタイル情報を付与したようなもの。
コードではスタイル情報は

/// Map from CSS property names to values.
type PropertyMap = HashMap<String, Value>;

/// A node with associated style data.
#[derive(Debug, PartialEq)]
pub struct StyledNode<'a> {
    pub node: &'a Node, // pointer to a DOM node
    pub specified_values: PropertyMap,
    pub children: Vec<StyledNode<'a>>,
}

というようにハッシュマップで持たせていた。

また、この specified_values を構築する処理は

/// Apply styles to a single element, returning the specified values.
fn specified_values(elem: &ElementData, stylesheet: &Stylesheet) -> PropertyMap {
    let mut values = HashMap::new();
    let mut rules = matching_rules(elem, stylesheet);

    // Go through the rules from lowest to highest specificity.
    rules.sort_by(|&(a, _), &(b, _)| a.cmp(&b));
    for (_, rule) in rules {
        for declaration in &rule.declarations {
            values.insert(declaration.name.clone(), declaration.value.clone());
        }
    }
    values
}

という関数でやってるんだけど、複数のルールが該当したときに詳細度が高いものが勝つように一度詳細度の低い順に並び替えてから順次ハッシュマップに insert、ということをやっていた(declaration.name が重複してるものは後の方で上書きされる)。

Part5, 6: Layout tree

Style tree から Layout tree を計算する。Layout tree は

struct LayoutBox<'a> {
    dimensions: Dimensions,
    box_type: BoxType<'a>,
    children: Vec<LayoutBox<'a>>,
}
  • box_type: 要素がブロック要素かインライン要素か(または none)
  • dimensions.content: コンテンツの x, y 座標と、縦横の大きさ
  • dimensions.padding, border, margin: padding, border, margin のそれぞれの上下左右幅

といった情報を持った木構造

DevTools でよく見るこのボックスをイメージするとわかりやすい。

f:id:dackdive:20210223110334p:plain

ここに、矩形左上の点の x, y 座標が追加されているイメージ。

ここの座標計算が一番複雑で、かつ元々このあたりの知識がなく理解が難しかったポイント。

  • ブロック要素は縦方向に並び、幅は親 (container) の幅いっぱいになる
  • インライン要素は横方向に並び、親の幅を超えそうになったら折り返す

という基本的なことに加え、ブロック要素とインライン要素が混在していた場合、インライン要素群のまわりに Anonymous Box と呼ばれるブロック要素が形成されるらしい。

f:id:dackdive:20210223105242p:plain

(画像は記事より引用)

また、要素の幅に関しては親に依存するが、高さはその要素が包含する子要素の高さに依存する。
そのため、子要素の幅・高さを計算する際に親要素の高さに子の分を足し合わせるといった処理が必要になる。
それは layout_block_children() でやっている。

Part 7: Painting

"Rasterization"(日本語だと "ラスタライズ"?)と呼ばれる描画処理。
Layout tree から直接描画するのではなく、一旦 "円を描け" "テキスト文字を描け" のような描画に関するコマンド( DisplayCommand )のリストを作り、最後にそのリストの通りに順番に描画する。

なぜ一旦コマンドに変換するかというと、いくつかの理由がある。

  • 後続のコマンドで完全にカバーされるコマンドを検索し、取り除いて無駄な描画を排除できる
  • 一部の要素しか変更されていないことを知っている場合、コマンドのリストを変更したり再利用できる
  • 同じコマンドリストから異なるアウトプット(たとえばスクリーン用にピクセル、プリンタ用にベクタ)を生成できる

("Building the Display List" の項を参照)

2020年のふりかえりと2021年にやりたいこと

去年もふりかえりしてたので今年も書く。

去年:

今年の四半期ごとのふりかえり:

年初に立てた目標

去年の今ごろには

  • 英語ブログ、今年こそ書く!目標は3本
  • 今年もどっかのカンファレンスにCfP出す
  • 「みんなのデータ構造」とあともう1冊、コンピュータサイエンスに関する本を読む
  • 本は年間で6冊読む

という目標を立てていたらしい。
そのうち、実践できたのは最後の「本は年間で6冊読む」だけ(ひどい)

英語学習や勉強会への登壇に対するモチベーションが途中から完全になくなってしまっていた反面、
Rust とか新しいことにチャレンジできたのは良かった点かな、と前向きに捉えることにする。

2020年をふりかえって

技術面

ざっくり、1年の前半はデータ構造とアルゴリズム、後半はRust、WebAssemblyそして最後にOAuth 2.0という感じだったはず。

みんなのデータ構造

みんなのデータ構造

  • 作者:Pat Morin
  • 発売日: 2018/07/20
  • メディア: 単行本(ソフトカバー)

去年〜今年4月ごろまで読んでた。
これは正直自分にとってはめちゃくちゃ難しかったんだけど、読んで良かったと思える本だった。

これに続く形でデータ構造とアルゴリズム関連の書籍を読み進められなかったのは反省点。

実践Rust入門[言語仕様から開発手法まで]

実践Rust入門[言語仕様から開発手法まで]

その後、5月〜7月あたりに読んだのがこの本。
まだまだ勉強不足で Rust は全然わからない。でも Rust 勉強してみたいな〜と思ってたので一歩踏み出せてよかった。

Rust を勉強したんだから WebAssembly にコンパイルして動かす方法も知っておきたい、と思って読んだのがこのあたり。
最初 Tour of WebAssembly 読んだけどさっぱりだった。

それから、今年の最後の方は今まで雰囲気で理解していた OAuth 2.0 を学び直していた。
本を読んだり RFC を読んだりすると新しい学びがある、というか OAuth のこと全然理解してなかったんだなーというのがわかってよかった。今もちゃんと理解できてない。

OAuth 2.0 に関しての学びは、つい先日 Zenn の方にアウトプットできた。

この記事を書く過程で、アウトプットってやっぱ大事だなーと痛感した。
書きはじめてから公開までなんだかんだ1ヶ月ぐらいかかってて、その間書こうと思ったらちゃんと理解できてないことに気づいて調べ直して...というのを何度も繰り返した。

生活面

今年はコロナによって働き方が大きく変わった一年だった。
小さい子供が2人いるので、これまでは在宅勤務なんて絶対無理と思って毎日通勤してたんだけど
コロナで強制的に在宅勤務を余儀なくされた結果、今ではすっかり適応できている。

それこそ在宅勤務がスタートした当初(3月ぐらい)は

  • 子供がうるさくて仕事に集中できない
  • 椅子が安物で疲労が溜まる、腰が痛くなる
  • 通勤時間がなくなって一人ぼーっとする時間がとれず仕事とプライベートの気持ちの切り替えができない

、、、などなど相当辛かった記憶があるが、少しずつ新しい働き方に適した生活リズムを作ることができて、ストレスなく仕事できる状態にはなった。
逆に、夕飯や風呂の時間を家族と過ごせるという在宅勤務のメリットは非常に大きいことにも気付かされ、以前の、片道1時間以上かけて通勤してた生活には戻れないと思う。

一方で、在宅勤務に慣れた9月ごろから、マンネリ感というかなんとなく毎日可もなく不可もなくという状態が続くようになったと感じている。
モチベーション的に大きく下がることもないが上がることもない、みたいな感じ。 これはたぶん、コロナによって行動が制限され、同僚と物理的に会ったり飲み会のようなコミュニケーションの機会がなくなったことや、毎日代わり映えのしない場所で仕事を続けることになったせいで、(ただでさえ少なかった)日常のメリハリが失われていることが原因だと思う。

これまではコロナ禍でも以前と変わらず仕事ができていることに感謝していたけど、今後は「どうやったらもっと楽しくなるか?」を考えて、意識的にアクションをとらないとこのままの状態がずるずる続きそう。

その他

四半期ごとにふりかえりを書くようにしたのはよかった。
1年をふりかえろうとしたときにだいたい当時何やってたか書いてあるし、あと目標管理のサイクルとして3ヶ月で達成できそうな目標を立てるのは自分には合ってそう。
興味がコロコロ変わるので1年前にやると決めたことをやるのはハードルが高すぎる。

2021年にやりたいこと

新たになにか始めたいというよりは、今年スタートだけはできたコンピュータサイエンス系の勉強やRust & WebAssembly の勉強を継続できるようにしたい。
あとは、意識して質の低いアウトプットを増やすことをやっていきたい。

コンピュータサイエンス(あるいは、低レイヤの勉強?)

今年のはじめにコンピュータサイエンスを勉強したいと思ったときは、そもそもコンピュータサイエンスという学問の中にどういった分野があるのかよくわかっておらず、ただその中でもデータ構造とアルゴリズムが一番イメージしやすく取り組みたいことだったので手を出した。

今は、データ構造とアルゴリズムに加えて、いわゆる低レイヤと呼ばれる領域に興味がある。
コンパイラとかOSとかを手を動かして作ってみたい。

というわけで12月後半にこの本を買って、Rust でやってみようとしている最中。

Go言語でつくるインタプリタ

Go言語でつくるインタプリタ

  • 作者:Thorsten Ball
  • 発売日: 2018/06/16
  • メディア: 単行本(ソフトカバー)

大学行きたい欲も依然としてあるものの、少なくとも来年は興味を持った領域を書籍などで自分のペースでやるのがいいんじゃないかなとも思っている。

leetcode

前述したように、データ構造とアルゴリズムについて書籍などで体系的に学ぶ意欲が薄れてきているので、今年の後半ほとんどできてなかった leetcode を来年は継続できるようにしたい。
曜日を決めて取り組むようにしてうまくリズムを作りたい。

Rust & WebAssembly

どちらもまだまだ勉強し始めたばかりというレベルなので、継続したい。
Rust は低レイヤの勉強とうまくからめて書くきっかけを作れるといいな。WebAssembly は今のところ作りたいものが特に思いつかないので、
本を読むのがいいんじゃないかと思ってる。

質の低いアウトプットを増やす

これは技術領域ではなく心構えの話なんだけど、年末にこちらのブログを読んで。

すごい共感できて、自分もここのブログは今まで以上に「駄文でも誰の役にも立たないかもしれなくてもとりあえず書く」を意識しようと思った。
雑なアウトプットはこちらで、もうちょっと他人に読まれることを意識したものは Zenn で、みたくうまくプラットフォームを使い分けたい。

Rustのモジュールシステムの基本

久しぶりに Rust を書いたらモジュール周り全然思い出せなかったのでメモ。
以下の公式ドキュメントの第7章を自分用にまとめつつ、調べたことを足した内容になります。

The Rust Programming Language 日本語版 7. 肥大化していくプロジェクトをパッケージ、クレート、モジュールを利用して管理する

パッケージ、クレート、モジュール

  • パッケージ:1つ以上のクレートを持ち、ある機能を提供するための単位
    • 1つの Cargo.toml で管理する粒度
    • 0個か1個のライブラリクレートを持つ
    • バイナリクレートはいくらでも持って良い
    • ライブラリクレートとバイナリクレートをあわせて少なくとも1つのクレートを持っていないといけない
  • クレート:ライブラリ(lib.rs)か実行可能ファイル(main.rs)を生成するモジュール群
    • ライブラリクレート: src/lib.rs
    • バイナリクレート: src/main.rs
      • または、 src/bin ディレクトリにファイルを置くことで複数のバイナリクレートを持つことができる
  • モジュール: moduse でまとめる単位

ので、ディレクトリ構成としては以下のようになる(後述するモジュール単位のファイルを除く)

<package>
  - Cargo.toml
  - src/
    - lib.rs     ... 0 or 1
    - main.rs    ... 0 or 1
    - bin/       ... 0 or N (files)
      - bin_a.rs
      - bin_b.rs
      - ...

モジュールの基本的なルール

mod キーワードでくくった範囲がモジュールとなり、スコープが分かれる

mod front_of_house {
    mod hosting {
      fn add_to_waitlist() {}
    }
}

mod back_of_house {}

これは、以下のようなモジュールツリーを形成する。

crate
  - front_of_house
    - hosting
      - add_to_waitlist
  - back_of_house

モジュールはパスを指定して呼び出す

mod front_of_house {
    mod hosting {
        fn add_to_waitlist() {}
    }
}

pub fn eat_at_restaurant() {
    // 絶対パス
    crate::front_of_house::hosting::add_to_waitlist();

    // 相対パス
    front_of_house::hosting::add_to_waitlist();
    self::front_of_house::hosting::add_to_waitlist();
}

モジュールはデフォルトで非公開。公開するには pub キーワードをつける

  • モジュールの公開範囲と、モジュールの中身の公開範囲は別々に指定する
mod front_of_house {
    pub mod hosting {
        // ここもpubにしないとadd_to_waitlist()関数にはアクセスできない
        pub fn add_to_waitlist() {}
    }
}


  • 構造体は各フィールドを個別に公開するかどうか決められる
  • enum は公開するとそのヴァリアントはすべて公開される
mod back_of_house {
    pub struct Breakfast {
        pub toast: String,
        seasonal_fruit: String, // このフィールドにはアクセスできない
    }

    pub enum Appetizer {
        Soup,
        Salad,
    }
}

pub fn eat_at_restaurant() {
    let meal = back_of_house::Breakfast {
        toast: String::from("Rye"),
        seasonal_fruit: String::from("blueberries"), // error
    };

    let order1 = back_of_house::Appetizer::Soup;
    let order2 = back_of_house::Appetizer::Salad;
}

use キーワード

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    // before:
    // crate:front_of_house::hosting::add_to_waitlist();

    hosting::add_to_waitlist();
}


  • 慣例的に、
    • 関数をuseで持ち込む場合、親モジュールをuseしてsome_module::func()と書く
      • パスを省略しつつ関数がローカルで定義されていないことを明らかにできるため
    • 構造体やenumuseで持ち込む場合、フルパスを書く
  • as で別名をつけることもできる。モジュール間で名前の衝突を回避するときなどに使える
use std::fmt::Result;
use std::io::Result as IoResult;


  • pub use を使うと名前を再公開(re-exporting)できる
    • 下の例では、このライブラリクレートを使う外部のコードも hosting::add_to_waitlist() を使えるようになる
// src/lib.rs
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

pub use crate::front_of_house::hosting;

モジュールを複数のファイルに分割する

https://doc.rust-jp.rs/book-ja/ch07-05-separating-modules-into-different-files.html

  • src/lib.rsmod xxx; という宣言だけ書くと、モジュールの中身は src/xxx.rs というファイルを見に行くようになる
// src/lib.rs
mod front_of_house;

pub use crate::front_of_house::hosting;
// src/front_of_house.rs
pub mod hosting {
    pub fn add_to_waitlist() {}
}


  • さらに、 src/xxx.rs 内で mod yyy; という宣言だけ書くと、モジュールの中身は src/xxx/yyy.rs というファイルを見に行くようになる
// src/lib.rs
mod front_of_house;

pub use crate::front_of_house::hosting;
// src/front_of_house.rs
pub mod hosting;
// src/front_of_house/hosting.rs
pub fn add_to_waitlist() {}

というわけで、ディレクトリ構成は以下のようになりそう。

- src/
  - lib.rs
  - module_a.rs # サブモジュール含む
  - module_a/
    - sub_module_a.rs
  - module_b.rs # サブモジュール含まない
  - ...
  • モジュールツリーとしては変わらないので、使う側の use 宣言や呼び出し方は何も変わらない

lib.rs で定義したモジュールを main.rs で使うには?

ドキュメントでは触れられていなかった部分。パスをパッケージ名から始める必要がある。

// Cargo.toml
[package]
name = "restaurant"
version = "0.1.0"
// src/main.rs
use restaurant::hosting;

fn main() {
    hosting::add_to_waitlist();
}

こうすると使えるようになるが、根拠となるドキュメントは見つけられてない。
Stackoverflow のこれは若干関係しそう。

Rust modules confusion when there is main.rs and lib.rs - Stack Overflow

余談: mod.rs

「Rust モジュールシステム」とかでググると、モジュールを xxx/mod.rs というファイルで定義する方法が紹介されてる記事が見つかるが、

Rustのモジュールの使い方 2018 Edition版 | κeenのHappy Hacκing Blog

を読む限り、これは 2015 Edition の頃の後方互換性のために用意されているらしい。

これ以上深追いはせず、素直に公式ドキュメントで案内されてる方法を使うことにする。

@octokit/rest.jsのテスト用モックサーバー(@octokit/fixtures-server)のしくみ

@octokit/rest.jsGitHub 社が提供する公式の REST API クライアントライブラリです。
以前この@octokit/rest.js のアーキテクチャがどうなっているか調査した 際、テストに @octokit/fixtures-server という別のパッケージが使われていることまではわかったものの、こいつが何をやっているのかまでは調べきれなかったので、今回はそのあたりを調査したメモです。

なお、 @octokit/rest.js は JavaScript 向けのクライアントライブラリですが、今回調べたモックサーバーについては特定の言語に依存せず利用できます。

  • 調査時のパッケージバージョン
  • 先にまとめ
  • 登場するパッケージとその役割
    • @octokit/fixtures
    • @octokit/fixtures-server
    • @octokit/rest.js
  • fixture ファイルを生成・管理するしくみ
    • 1. 各 scenario ディレクトリ下の record.js に従い、GitHubREST API を実行する
    • 2. レスポンスデータの正規化(normalization)を行う
    • 3. 2の結果と、すでに保存済みの normalized-fixture.json と比較し、差分があれば出力する
    • 保存済みの fixture ファイルと実際の REST API レスポンスに乖離がないか、定期的にチェックする
  • おまけ: bin/record.js を自分で実行してみる
    • 2. アクセストークンを発行し、環境変数にセットする
    • 3. 新しい scenario ディレクトリを作成し、record.js を定義する
    • 4. bin/record.js を実行する
    • 5. bin/record.js を --update オプションつきで実行する
    • 6. リポジトリの内容を編集し、再度 bin/record.js を実行する
続きを読む