cargo buildしたら「error: linker `link.exe` not found」が出たとき

そのまんまなタイトルですが、備忘録のために解決方法を記載しておきます。 Windows11でRustの開発中、cargo buildしたらこんな感じでエラーが出ました。 error: linker `link.exe` not found | = note: program not found note: the msvc targets depend on the msvc linker but `link.exe` was not found note: please ensure that Visual Studio 2017 or later, or Build Tools for Visual Studio were installed with the Visual C++ option. note: VS Code is a different product, and is not sufficient. wingetコマンドでビルドツールをインストールしてあげましょう。 winget install Microsoft.VisualStudio.2022.BuildTools これが終わったら、Visual Studio Installerを起動します。 インストール済みのところに先ほどインストールしたビルドツールが表示されています。 ここの変更を押します。 ...

10月 1, 2023 · にあえん

RustでZaimと東京ガスを連携させてみた【1】

最近、昨年の医療費控除を受けるために確定申告の準備などをしているのですが、家計簿ソフトで利用しているZaimにガスや水道代が勝手に計上されてくれると便利だな〜と思い、Zaim APIとSeleniumを使って実現してみようと思います。 Zaim Developers Center 東京ガスのCSVファイルをダウンロードする Rustのheadless-chromeを使って構築します。 とりあえずCargoパッケージ作ってライブラリを追加します。 cargo new sync-zaim cargo add headless-chrome 追加できました。 使い勝手を知るために、まずはGoogleにアクセスしてスクショをとってみます。 use headless_chrome::{protocol::cdp::Page::CaptureScreenshotFormatOption, Browser}; use std::fs; fn main() { let browser = Browser::default().unwrap(); let tab = browser.new_tab().unwrap(); tab.set_default_timeout(std::time::Duration::from_secs(200)); tab.navigate_to("https://www.google.com/").unwrap(); tab.wait_until_navigated().unwrap(); let jpeg_data = tab.capture_screenshot( CaptureScreenshotFormatOption::Jpeg, None, None, true, ).unwrap(); fs::write("screenshot.jpg", jpeg_data).unwrap(); } 実行すると、Googleの画面のスクショが保存されました。 この調子で東京ガスのCSVを取得できるようにしましょう。 コードはpythonですがここを参考にします。 Python(Selenium)で東京ガスのcsvをダウンロード|gadgetking 実行すると少しエラーは出ますが、ファイルダウンロードは可能になりました。 use headless_chrome::{protocol::cdp::Page::CaptureScreenshotFormatOption, Browser}; use headless_chrome::protocol::cdp::types::Method; use std::sync::Arc; use std::{env, thread, time, error::Error}; use std::{fs, path}; use serde::Serialize; const TOKYO_GAS_LOGIN_URL: &str = "https://members.tokyo-gas.co.jp/login.html"; const GAS_ID: &str = "東京ガスのログインID"; const GAS_PASSWD: &str = "東京ガスのログインパスワード"; // https://github.com/rust-headless-chrome/rust-headless-chrome/issues/187 #[derive(Serialize, Debug)] struct Command { behavior: &'static str, downloadPath: &'static str, } impl Method for Command { const NAME: &'static str = "Page.setDownloadBehavior"; type ReturnObject = serde_json::Value; } fn main() -> Result<(), Box<dyn Error>> { let browser = Browser::default().unwrap(); let tab = browser.new_tab().unwrap(); tab.set_default_timeout(std::time::Duration::from_secs(200)); // https://github.com/rust-headless-chrome/rust-headless-chrome/issues/280 tab.enable_request_interception(Arc::new(|transport, session_id, params| { println!("request!"); println!("transport: {:?}", transport); println!("session_id: {:?}", session_id); println!("params: {:?}", params); headless_chrome::browser::tab::RequestPausedDecision::Continue(None) }))?; tab.navigate_to(TOKYO_GAS_LOGIN_URL).unwrap(); tab.wait_until_navigated().unwrap(); let jpeg_data = tab.capture_screenshot( CaptureScreenshotFormatOption::Jpeg, None, None, true, ).unwrap(); fs::write("screenshot.jpg", jpeg_data).unwrap(); tab.wait_for_element("#loginId").unwrap().type_into(GAS_ID).unwrap(); tab.wait_for_element("#password").unwrap().type_into(GAS_PASSWD).unwrap(); tab.wait_for_element("#submit-btn").unwrap().click().unwrap(); // tab.wait_until_navigated().unwrap(); thread::sleep(time::Duration::from_secs(5)); let jpeg_data = tab.capture_screenshot( CaptureScreenshotFormatOption::Jpeg, None, None, true, ).unwrap(); fs::write("screenshot.jpg", jpeg_data).unwrap(); let path_string = env::current_dir()?.into_os_string().into_string().unwrap(); // println!("pwd: {}", path_string); let static_path_str: &'static str = Box::leak(path_string.into_boxed_str()); let command = Command { behavior: "allow", downloadPath: static_path_str, }; tab.call_method(command)?; // CSVファイルをダウンロード tab.navigate_to("https://members.tokyo-gas.co.jp/api/mieru/chargeAmountCsv.jsp?no=0&target=total") .unwrap(); thread::sleep(time::Duration::from_secs(30)); Ok(()) } 実行すると以下のようなエラーが出ます。 ...

9月 10, 2023 · にあえん

Rustで100本ノック【1】

ろくにRustに触らないままでwasmをやってしまっていましたが、ちゃんと触っておこうと思って100本ノックに挑みました。 言語処理100本ノック 2015 その際の備忘録を残します。 Q00: 文字列の逆順 逆順にするだけなのに、結局ググってしまった。。 Conversion between String, str, Vec<u8>, Vec<char> in Rust · GitHub fn main() { // 文字列"stressed"の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ. let s = "stressed"; let mut s = s.chars().collect::<Vec<_>>(); s.reverse(); print!("{}", s.iter().collect::<String>()); } collectの使い方とか、pythonみたいに容易に行かない感じがする。 Q01 さっきよりかはスムーズにできた。 でもイテレータの使い方とかちょっとググった。 fn main() { // 「パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ. let s = "パタトクカシーー"; for (i, c) in s.chars().enumerate() { if !([1,3,5,7].contains(&i)) { continue; } print!("{}", c); } println!(); } Q02 基本的に先程の2問と解き方は一緒だった。 fn main() { // 「パトカー」+「タクシー」の文字を先頭から交互に連結して文字列「パタトクカシーー」を得よ. let s1 = "パトカー"; let s2 = "タクシー"; let mut s1_vec = s1.chars().collect::<Vec<_>>(); let mut s2_vec = s2.chars().collect::<Vec<_>>(); s1_vec.reverse(); s2_vec.reverse(); let mut result: Vec<char> = vec![]; while s1_vec.len() > 0 && s2_vec.len() > 0 { if s1_vec.len() > 0 { result.push(s1_vec.pop().unwrap()); } if s2_vec.len() > 0 { result.push(s2_vec.pop().unwrap()); } } println!("{}", result.iter().collect::<String>()); } Q03 前の問題に比べると少し楽になりました。 ...

8月 20, 2023 · にあえん

オセロゲームなどで自分のコマの周りがどうなっているかを取得する

wasmのチュートリアルを実施した際に見つけたやつ。 詳しくはこちら fn live_neighbor_count(&self, row: u32, column: u32) -> u8 { let mut count = 0; for delta_row in [self.height - 1, 0, 1].iter().cloned() { for delta_col in [self.width - 1, 0, 1].iter().cloned() { if delta_row == 0 && delta_col == 0 { continue; } let neighbor_row = (row + delta_row) % self.height; let neighbor_col = (column + delta_col) % self.width; let idx = self.get_index(neighbor_row, neighbor_col); count += self.cells[idx] as u8; } } count } パフォーマンス改良版はこれ。 (こっちはこの記事を参考) fn live_neighbor_count(&self, row: u32, column: u32) -> u8 { let mut count = 0; let north = if row == 0 { self.height - 1 } else { row - 1 }; let south = if row == self.height - 1 { 0 } else { row + 1 }; let west = if column == 0 { self.width - 1 } else { column - 1 }; let east = if column == self.width - 1 { 0 } else { column + 1 }; let nw = self.get_index(north, west); count += self.cells[nw] as u8; let n = self.get_index(north, column); count += self.cells[n] as u8; let ne = self.get_index(north, east); count += self.cells[ne] as u8; let w = self.get_index(row, west); count += self.cells[w] as u8; let e = self.get_index(row, east); count += self.cells[e] as u8; let sw = self.get_index(south, west); count += self.cells[sw] as u8; let s = self.get_index(south, column); count += self.cells[s] as u8; let se = self.get_index(south, east); count += self.cells[se] as u8; count }

8月 19, 2023 · にあえん

wasmに入門してみる【8】

今回で最後になります。 wasmに入門してみる【1】 wasmに入門してみる【2】 wasmに入門してみる【3】 wasmに入門してみる【4】 wasmに入門してみる【5】 wasmに入門してみる【6】 wasmに入門してみる【7】 wasmのサイズを小さくする いくつかの設定を追記することで、wasmのサイズを小さくすることができます。 Shrinking .wasm Size - Rust and WebAssembly Rustのリリースビルドみたいなものですね。 まず必要なパッケージをインストールします。 cargo install wasm-opt Cargo.tomlに設定を追記します。 [profile.release] lto = true opt-level = "z" 再ビルドする前に、一度どのくらいの大きさなのか見ておきましょう。 % wc -c pkg/wasm_game_of_life_bg.wasm 25136 pkg/wasm_game_of_life_bg.wasm 大体25KBくらいでした。 では先程インストールしたwasm-optで最適化していきます。 wasm-opt -Oz -o output.wasm pkg/wasm_game_of_life_bg.wasm 最適化できました。 % wc -c output.wasm 24955 output.wasm あんまり変わっていないですね。。 wasm-packでもビルドしてみて、wasmファイルに変化があるか確認してみます。 %wasm-pack build [INFO]: 🎯 Checking for the Wasm target... [INFO]: 🌀 Compiling to Wasm... Compiling wasm-game-of-life v0.1.0 (/home/username/Project/personal/wasm-game-of-life) warning: unused macro definition: `log` --> src/lib.rs:25:14 | 25 | macro_rules! log { | ^^^ | = note: `#[warn(unused_macros)]` on by default warning: `wasm-game-of-life` (lib) generated 1 warning Finished release [optimized] target(s) in 0.41s [INFO]: ⬇️ Installing wasm-bindgen... [INFO]: found wasm-opt at "/home/username/.cargo/bin/wasm-opt" [INFO]: Optimizing wasm binaries with `wasm-opt`... [INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended [INFO]: ✨ Done in 0.86s [INFO]: 📦 Your wasm pkg is ready to publish at /home/username/Project/personal/wasm-game-of-life/pkg. ビルドできたので確認します。 ...

8月 18, 2023 · にあえん

wasmに入門してみる【7】

前回からの続きです。 wasmに入門してみる【1】 wasmに入門してみる【2】 wasmに入門してみる【3】 wasmに入門してみる【4】 wasmに入門してみる【5】 wasmに入門してみる【6】 今回はタイムプロファイリングの章です。 Time Profiling - Rust and WebAssembly タイムプロファイリングする ライフゲームのパフォーマンス向上の指針として、FPS(Frames Per Second)タイマーの導入を行います。 window.performance.nowという関数を利用して、 www/index.jsにfpsオブジェクトを実装します。 const fps = new class { constructor() { this.fps = document.getElementById("fps"); this.frames = []; this.lastFrameTimeStamp = performance.now(); } render() { // 最後のフレームレンダリングからの差分時間を1秒あたりのフレーム数に変換する。 const now = performance.now(); const delta = now - this.lastFrameTimeStamp; this.lastFrameTimeStamp = now; const fps = 1 / delta * 1000; // 最新の100個のタイミングだけを保存する。 this.frames.push(fps); if (this.frames.length > 100) { this.frames.shift(); } // 最新の100個のタイムの最大値、最小値、平均値を求めます。 let min = Infinity; let max = -Infinity; let sum = 0; for (let i = 0; i < this.frames.length; i++) { sum += this.frames[i]; min = Math.min(this.frames[i], min); max = Math.max(this.frames[i], max); } let mean = sum / this.frames.length; // fpsに描画する this.fps.textContent = ` Frames per Second: latest = ${Math.round(fps)} avg of last 100 = ${Math.round(mean)} min of last 100 = ${Math.round(min)} max of last 100 = ${Math.round(max)} `.trim(); } }; //...中略 const renderLoop = () => { fps.render(); // 追記 universe.tick(); drawGrid(); drawCells(); animationId = requestAnimationFrame(renderLoop); }; www/index.htmlにも要素を追加します。 ...

8月 17, 2023 · にあえん

wasmに入門してみる【6】

前回からの続きです。 wasmに入門してみる【1】 wasmに入門してみる【2】 wasmに入門してみる【3】 wasmに入門してみる【4】 wasmに入門してみる【5】 ゲームの一時停止と再生機能 まずはゲームの一時停止と再生ができるようにコードを修正します。 実装はJS内のみで完結できます。 www/index.htmlに新しくボタンを追加します。 <body> <noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript> <!-- ボタンを追加 --> <button id="play-pause"></button> <canvas id="game-of-life-canvas"></canvas> <script src="./bootstrap.js"></script> </body> 次にこのボタンに対するハンドラの実装を行います。 // requestAnimationFrameが発行する返り値を格納する変数 // https://developer.mozilla.org/ja/docs/Web/API/window/requestAnimationFrame let animationId = null; // 再生/停止を行うボタンを取得 const playPauseButton = document.getElementById("play-pause"); // 再生時のハンドラを実装 const play = () => { playPauseButton.textContent = "⏸"; renderLoop(); }; // 停止時のハンドラを実装 const pause = () => { playPauseButton.textContent = "▶"; cancelAnimationFrame(animationId); animationId = null; }; // 停止しているか判定する関数 const isPaused = () => { return animationId === null; }; // 再生/停止ボタンにイベントリスナーを追加 playPauseButton.addEventListener("click", (event) => { if (isPaused()) { play(); } else { pause(); } }); const renderLoop = () => { universe.tick(); drawGrid(); drawCells(); animationId = requestAnimationFrame(renderLoop); // requestAnimationFrameの返り値を設定 }; drawGrid(); drawCells(); play(); // requestAnimationFrame(renderLoop);をplay関数に置き換え これで再生/停止ボタンが使えるようになります! ...

8月 12, 2023 · にあえん

wasmに入門してみる【5】

シリーズ化しているwasm入門です。 (wasmのチュートリアルをやったという実績残しと備忘録を兼ねてやっています。真新しいことはないので悪しからず。。) wasmに入門してみる【1】 wasmに入門してみる【2】 wasmに入門してみる【3】 wasmに入門してみる【4】 テストの実装 これまでで実装してきたライフゲームのテストを実装します。 Testing Life - Rust and WebAssembly まずはUniverseの幅と高さのセッターを実装します。 #[wasm_bindgen] impl Universe { // ... /// Universeの幅を設定します。 /// /// すべてのセルの状態を死んだ状態にします。 pub fn set_width(&mut self, width: u32) { self.width = width; self.cells = (0..width * self.height).map(|_i| Cell::Dead).collect(); } /// Universeの高さを設定します。 /// /// すべてのセルの状態を死んだ状態にします。 pub fn set_height(&mut self, height: u32) { self.height = height; self.cells = (0..self.width * height).map(|_i| Cell::Dead).collect(); } // ... } 次に、セルのゲッターとセッターを実装します。 ここで、セルのゲッターを#[wasm_bindgen]マクロのあるUniverseに実装するとエラーになります。 #[wasm_bindgen] impl Universe { // ... /// Get the dead and alive values of the entire universe. pub fn get_cells(&self) -> &[Cell] { &self.cells } // ... } この状態でビルドします。 ...

8月 11, 2023 · にあえん

wasmに入門してみる【4】

前回の続きです。 wasmに入門してみる【1】 wasmに入門してみる【2】 wasmに入門してみる【3】 キャンバスタグを使って描画する 前回はpreタグを使って文字列で描画していました。 今回はキャンバスタグで描画するようにします。 前回preタグで宣言していた部分をそのままcanvasにします。 <canvas id="game-of-life-canvas"></canvas> Universeに高さ、幅、セルを返却するゲッターを実装します。 これはJSに公開されるように、#[wasm_bindgen]マクロを使用する必要があります。 #[wasm_bindgen] impl Universe { // ... pub fn width(&self) -> u32 { self.width } pub fn height(&self) -> u32 { self.height } pub fn cells(&self) -> *const Cell { self.cells.as_ptr() } // ... } 再ビルドもしておきましょう。 wasm-pack build 次にJSを修正します。 以前までの実装を削除し、新たに以下の実装を追加します。 セルのサイズや、盤上の色を定義します。 import { Universe, Cell } from "wasm-game-of-life"; const CELL_SIZE = 5; // px const GRID_COLOR = "#CCCCCC"; const DEAD_COLOR = "#FFFFFF"; const ALIVE_COLOR = "#000000"; 次に、マスを成形するためのグリッドを描画する関数を実装します。 // グリッド(マスを形成するための線)を描画します const drawGrid = () => { ctx.beginPath(); ctx.strokeStyle = GRID_COLOR; // Vertical lines. for (let i = 0; i <= width; i++) { ctx.moveTo(i * (CELL_SIZE + 1) + 1, 0); ctx.lineTo(i * (CELL_SIZE + 1) + 1, (CELL_SIZE + 1) * height + 1); } // Horizontal lines. for (let j = 0; j <= height; j++) { ctx.moveTo(0, j * (CELL_SIZE + 1) + 1); ctx.lineTo((CELL_SIZE + 1) * width + 1, j * (CELL_SIZE + 1) + 1); } ctx.stroke(); }; 次の実装はかなり興味深いです。 ...

8月 10, 2023 · にあえん

wasmに入門してみる【3】

前回の続きです。 wasmに入門してみる【1】 wasmに入門してみる【2】 wasmに入門してみる【4】 ライフゲームの実装(Rust側) うっすらイメージがついてきたところで実装に入ります。 まずは、src/lib.rsの内容を以下の実装で上書きします。 use wasm_bindgen::prelude::*; // セルの状態を表す列挙型を定義 #[wasm_bindgen] #[repr(u8)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Cell { Dead = 0, Alive = 1, } #[repr(u8)]を定義すると、Enumのプロパティが1バイトとして表現できます。 また、生存を1として表現することで、周囲のセルの生存状況を単純な加算で取得することができます。 続けてライフゲームの盤上である宇宙を表現します。 // ライフゲームの盤上(宇宙)の表現を行う構造体 #[wasm_bindgen] pub struct Universe { width: u32, // 横幅 height: u32, // 縦幅 cells: Vec<Cell>, // 盤上の生存状況 } 特定の行と列にアクセスするために、前回説明した式を実装します。 index(row, column, universe) = row * width(universe) + column これは宇宙の実装として用意します。 impl Universe { fn get_index(&self, row: u32, column: u32) -> usize { (row * self.width + column) as usize } } また、セルの次の状態を計算するため、生存しているセルをカウントする関数もUniverseに実装してあげます。 ...

8月 9, 2023 · にあえん