逆引き風 roxmltree の基礎的な使い方のメモ; rust のたぶん今の所いちばん速くて安全な XML ぱーさー
- https://crates.io/crates/roxmltree/
- https://github.com/RazrFalcon/roxmltree/
- https://docs.rs/roxmltree/0.13.0/roxmltree/
note: このメモは roxmltree-0.13.0 の時代に書きました。
はじめに知っておくとよいこと
- 与えられた XML ≈
Document
をNode
の木構造に分解してNode
を基準に操作するための crate =roxmltree
Node
は XML の Element (≈タグ)とは限りません。Node
が出てきたらカモシレナイ match/if が必要なパターンがありますancestor
は上位(先祖)、sbling
は同位(姉妹)、descendant
は下位(子孫)parent
は直近の上位(親)、child
は直近の下位(子)
XMLを開く
let document: roxmltree::Document = roxmltree::Document::parse( "<root/>" );
- https://docs.rs/roxmltree/0.13.0/roxmltree/struct.Document.html#method.parse
- note: ここから先で
document
が出てきたらこの続きと思って下さい。
XMLのルート要素の Node
を取得する
let node: roxmltree::Node = d.root_element();
- https://docs.rs/roxmltree/0.13.0/roxmltree/struct.Document.html#method.root_element
- note:
.root()
はドキュメントそのもの的なNode
が帰ってきます。 - note: ここから先で
node
が出てきたらroxmltree::Node
な変数だと思って下さい。
Node
が Element なのか何なのか確認する
match node.node_type() { roxmltree::NodeType::Element => println!("This Node is an Element."), _ => println!("This Node is NOT an Element.") }
または
let is_element: bool = node.is_element(); let is_comment: bool = node.is_comment();
- https://docs.rs/roxmltree/0.13.0/roxmltree/struct.Node.html#method.node_type
- https://docs.rs/roxmltree/0.13.0/roxmltree/struct.Node.html#method.is_root
- https://docs.rs/roxmltree/0.13.0/roxmltree/struct.Node.html#method.is_element
- https://docs.rs/roxmltree/0.13.0/roxmltree/struct.Node.html#method.is_pi
- https://docs.rs/roxmltree/0.13.0/roxmltree/struct.Node.html#method.is_comment
- https://docs.rs/roxmltree/0.13.0/roxmltree/struct.Node.html#method.is_text
Element な Node
のタグ名を確認または取得する
// <xxx> か判定 let is_xxx_tag: bool = node.has_tag_name("xxx"); // タグ名を取得 let tag_name: roxmltree::ExpandedName = node.tag_name(); // タグ名のローカル名部分を取得; <abbrns:localpart> の localpart の部分 let tag_local_name: &str = tag_name.name(); // タグ名の名前空間部分を取得; <abbrns:localpart> の abbrns の部分の xmlns: 定義の uri 値 let tag_namespace: &str = tag_name.namespace();
- note:
ExpandedName
の.namespace()
は<abbrns:localname>
に対してabbrns
ではなくxmlns:abbrns="http://example.com/"
を解決したhttp://example.com/
を取得します。 - https://docs.rs/roxmltree/0.13.0/roxmltree/struct.Node.html#method.has_tag_name
- https://docs.rs/roxmltree/0.13.0/roxmltree/struct.Node.html#method.tag_name
- https://docs.rs/roxmltree/0.13.0/roxmltree/struct.ExpandedName.html
- https://docs.rs/roxmltree/0.13.0/roxmltree/struct.ExpandedName.html#method.name
- https://docs.rs/roxmltree/0.13.0/roxmltree/struct.ExpandedName.html#method.namespace
XML Namespace 群を確認する
for ns: &roxmltree::Namespace in document.root_element().namespace() { println!("name={:?} uri={}", ns.name(), ns.uri()); }
- Note: 任意の
Node
で実装できますが、実際に XML でxmlns:
群が定義されている Element なNode
じゃないと何も出てきません。 - https://docs.rs/roxmltree/0.13.0/roxmltree/struct.Node.html#method.namespaces
Node
の Attribute 群を取得する
for attribute: &roxmltree::Attribute in node.attributes() { println!("name={} value={}", attribute.name(), attribute.value()); }
- https://docs.rs/roxmltree/0.13.0/roxmltree/struct.Node.html#method.attributes
- https://docs.rs/roxmltree/0.13.0/roxmltree/struct.Attribute.html
Node
の直近の下位(子) Element な Node
群を取得、または同位(姉妹)の Node
群を取得など
// 手法 A; .children から Element を .filter let child_elements = document.root_element().children().filter(|n|n.node_type()==roxmltree::NodeType::Element); for node: roxmltree::Node in child_elements { println!("name={} text={:?}", node.tag_name().name(), node.text() ); }
// 手法 B; .first_element_child から Option を while して .next_sibling_element let mut node_maybe = document.root_element().first_element_child(); while let Some(node) = node_maybe { println!("name={} text={:?}", node.tag_name().name(), node.text() ); node_maybe = node.next_sibling_element(); }
- 残念ながら
Node
に.element_children
的な関数はいまのところ無いので.children
から.filter
します。 - https://docs.rs/roxmltree/0.13.0/roxmltree/struct.Node.html#method.children
- https://docs.rs/roxmltree/0.13.0/roxmltree/struct.Node.html#method.first_element_child
Node
の上位(先祖)群、直近の上位(親)、下位(子孫)群
// 上位群 let ancentors: AxisIter = node.ancestors(); for node: roxmltree::Node in ancentors { println!("ancentor name={} text={:?}", node.tag_name().name(), node.text() ); } // 直近の上位 let parent_maybe: Option<roxmltree::Node> = node.parent_element(); let node: roxmltree::Node = parent_maybe.unwrap(); println!("parent name={} text={:?}", node.tag_name().name(), node.text() ); // 下位群 let descendants: Descendants = node.descendants(); for node: roxmltree::Node in descendants { println!("descendant name={} text={:?}", node.tag_name().name(), node.text() ); }
AxisIter
≈ [.parent()
,.parent().parent()
->.parent().parent().parent()
, .. ] 的に列挙してくれるやつDescendants
≈ 再帰的に.children
で下位Node
を根こそぎして.flatten
したような、下位Node
すべてがごそっと取り出せるやつ- https://docs.rs/roxmltree/0.13.0/roxmltree/struct.Node.html#method.ancestors
- https://docs.rs/roxmltree/0.13.0/roxmltree/struct.Node.html#method.parent_element
- https://docs.rs/roxmltree/0.13.0/roxmltree/struct.Node.html#method.descendants
小さな XML のパース速度で比べる rust の XML crate たち; roxmltree vs. minidom ≈ quick-xml vs. sxd-document vs. sxd-xpath vs. amxml
結果
test benches::amxml_root ... bench: 40,783 ns/iter (+/- 6,365) test benches::amxml_sec ... bench: 28,487 ns/iter (+/- 1,295) test benches::minidom_root ... bench: 1,942 ns/iter (+/- 73) test benches::minidom_sec ... bench: 2,127 ns/iter (+/- 1,183) test benches::roxmltree_root ... bench: 999 ns/iter (+/- 36) test benches::roxmltree_sec ... bench: 1,171 ns/iter (+/- 205) test benches::sxd_document_root ... bench: 3,453 ns/iter (+/- 123) test benches::sxd_document_sec ... bench: 3,566 ns/iter (+/- 136) test benches::sxd_xpath_root ... bench: 13,206 ns/iter (+/- 2,809) test benches::sxd_xpath_sec ... bench: 16,499 ns/iter (+/- 1,370)
- 速度: roxmltree >> minidom >> sxd-document >>>> sxd-xpath >>>>>>>> amxml 👉 roxmltree はやい
- sxd-document vs. sxd-xpath 👉 同じ操作でも XPath を噛むと4倍処理時間が長くなってしまう
そーす
Cargo.toml/dependencies:
[dependencies] roxmltree = "0.13.0" sxd-xpath = "0.4.2" sxd-document = "0.3.2" amxml = "0.5.3" quick-xml = "0.18.1"
benches/bench.rs:
#![feature(test)] extern crate test; include!("../tests/test.rs"); #[cfg(test)] mod benches { use test::Bencher; #[bench] fn minidom_root(bencher: &mut Bencher) { bencher.iter(|| crate::tests::minidom_root()); } #[bench] fn minidom_sec(bencher: &mut Bencher) { bencher.iter(|| crate::tests::minidom_sec()); } #[bench] fn roxmltree_root(bencher: &mut Bencher) { bencher.iter(|| crate::tests::roxmltree_root()); } #[bench] fn roxmltree_sec(bencher: &mut Bencher) { bencher.iter(|| crate::tests::roxmltree_sec()); } #[bench] fn amxml_root(bencher: &mut Bencher) { bencher.iter(|| crate::tests::amxml_root()); } #[bench] fn amxml_sec(bencher: &mut Bencher) { bencher.iter(|| crate::tests::amxml_sec()); } #[bench] fn sxd_document_root(bencher: &mut Bencher) { bencher.iter(|| crate::tests::sxd_document_root()); } #[bench] fn sxd_document_sec(bencher: &mut Bencher) { bencher.iter(|| crate::tests::sxd_document_sec()); } #[bench] fn sxd_xpath_root(bencher: &mut Bencher) { bencher.iter(|| crate::tests::sxd_xpath_root()); } #[bench] fn sxd_xpath_sec(bencher: &mut Bencher) { bencher.iter(|| crate::tests::sxd_xpath_sec()); } }
tests/test.rs:
#[cfg(test)] mod tests { const INPUT: &str = "<abc><x/><y/><z/></abc>"; #[test] pub fn minidom_root() { let root: minidom::Element = INPUT.parse().unwrap(); let root_name = root.name(); assert_eq!(root_name, "abc"); } #[test] pub fn minidom_sec() { let root: minidom::Element = INPUT.parse().unwrap(); let sec_names: Vec<&str> = root.children().into_iter().map(|element| element.name()).collect(); assert_eq!(sec_names, ["x", "y", "z"]) } #[test] pub fn roxmltree_root() { let document = roxmltree::Document::parse(INPUT).unwrap(); let root_name = document.root_element().tag_name().name(); assert_eq!(root_name, "abc"); } #[test] pub fn roxmltree_sec() { let document = roxmltree::Document::parse(INPUT).unwrap(); let sec_nodes = document.root_element().children(); let sec_names: Vec<&str> = sec_nodes.into_iter().map(|node| node.tag_name().name()).collect(); assert_eq!(sec_names, ["x", "y", "z"]) } #[test] pub fn amxml_root() { let document = amxml::dom::new_document(INPUT).unwrap(); // let xpath_result = document.eval_xpath("/*"); // let root_name = xpath_result.unwrap().get_item(0).as_nodeptr().unwrap().name(); let xpath_result = document.eval_xpath("/*/name()"); let root_name = xpath_result.unwrap().get_item(0).to_string(); assert_eq!(root_name, "\"abc\""); } #[test] pub fn amxml_sec() { let mut sec_names: Vec<String> = vec![]; let document = amxml::dom::new_document(INPUT).unwrap(); document .each_node("/*/*", |node| { sec_names.push(node.name().clone()); }) .unwrap(); assert_eq!(sec_names, ["x", "y", "z"]) } #[test] pub fn sxd_document_root() { let package = sxd_document::parser::parse(INPUT).unwrap(); let document = package.as_document(); let root_element = document.root().children().first().unwrap().element().unwrap(); let root_name = root_element.name().local_part(); assert_eq!(root_name, "abc"); } #[test] pub fn sxd_document_sec() { let package = sxd_document::parser::parse(INPUT).unwrap(); let document = package.as_document(); let root_element = document.root().children().first().unwrap().element().unwrap(); let sec_elements = root_element.children(); let sec_names: Vec<&str> = sec_elements .iter() .map(|child_of_element| child_of_element.element().unwrap().name().local_part()) .collect(); assert_eq!(sec_names, ["x", "y", "z"]) } #[test] pub fn sxd_xpath_root() { let package = sxd_document::parser::parse(INPUT).unwrap(); let document = package.as_document(); let xpath_result = sxd_xpath::evaluate_xpath(&document, "/*"); let value = xpath_result.unwrap(); let root_name = match value { sxd_xpath::Value::Nodeset(nodes) => { let node = nodes.iter().next().unwrap(); let qname = node.expanded_name().unwrap(); qname.local_part() }, _ => panic!("Failed: `Value` -> `Nodeset`") }; assert_eq!(root_name, "abc"); } #[test] pub fn sxd_xpath_sec() { let package = sxd_document::parser::parse(INPUT).unwrap(); let document = package.as_document(); let xpath_result = sxd_xpath::evaluate_xpath(&document, "/*/*"); let value = xpath_result.unwrap(); let sec_names: Vec<String> = match value { sxd_xpath::Value::Nodeset(nodes) => { nodes .iter() .map(|node| node.expanded_name().unwrap().local_part().to_string()) .collect() }, _ => panic!("Failed: `Value` -> `Nodeset`") }; let expected = ["x", "y", "z"]; assert_eq!(sec_names.len(), expected.len()); for element in expected.iter() { assert!(sec_names.contains(&element.to_string())); } } }
rust で XPath できる crate たちのメモ; (1) amxml, (2) sxd-xpath
XPath 使いたい需要に対応できる XML パーサーを crates.io で探すと有用そうな crate が2つ見つかりました。簡単な XPath で使い勝手を確認したメモです。
- amxml
- sxd-xpath ( + sxd-document )
1. amxml
cargo add amxml
#[cfg(test)] mod tests { /// 共通: 入力XML const INPUT: &str = "<abc><x/><y/><z/></abc>"; /// ルートノードの名前をXPathで確認 #[test] fn amxml_root() { let document = amxml::dom::new_document(INPUT).unwrap(); let xpath_result = document.eval_xpath("/*"); let root_name = xpath_result.unwrap().get_item(0).as_nodeptr().unwrap().name(); assert_eq!(root_name, "abc"); } /// 第2階層(=ルート直下)ノード群の名前をXPathで確認 #[test] fn amxml_sec() { let mut sec_names: Vec<String> = vec![]; let document = amxml::dom::new_document(INPUT).unwrap(); document .each_node("/*/*", |node| { sec_names.push(node.name().clone()); }) .unwrap(); assert_eq!(sec_names, ["x", "y", "z"]); } }
- 👍 嬉しいところ:
- ⚠ 注意が必要なところ:
// 参考おまけ: note(†) let xpath_result = document.eval_xpath("/*/name()"); // XPath では /name() で要素名の文字列値を取れる let root_name = xpath_result.unwrap().get_item(0).to_string(); // XPathの結果がノードではなく文字列値なので Item::to_string を使える assert_eq!(root_name, "\"abc\""); // 文字列リテラル表現で取れてくるので比較など後の処理で"文字列リテラルな文字列"を気にする事になります
2. sxd-xpath ( + sxd-document )
cargo add sxd-xpath sxd-document
#[cfg(test)] mod tests { /// 共通: 入力XML const INPUT: &str = "<abc><x/><y/><z/></abc>"; /// ルートノードの名前をXPathで確認 #[test] fn sxd_root() { let package = sxd_document::parser::parse(INPUT).unwrap(); let document = package.as_document(); let xpath_result = sxd_xpath::evaluate_xpath(&document, "/*"); let value = xpath_result.unwrap(); let root_name = match value { sxd_xpath::Value::Nodeset(nodes) => { let node = nodes.iter().next().unwrap(); let qname = node.expanded_name().unwrap(); qname.local_part() }, _ => panic!("Failed: `Value` -> `Nodeset`") }; assert_eq!(root_name, "abc"); } /// 第2階層(=ルート直下)ノード群の名前をXPathで確認 #[test] fn sxd_sec() { let package = sxd_document::parser::parse(INPUT).unwrap(); let document = package.as_document(); let xpath_result = sxd_xpath::evaluate_xpath(&document, "/*/*"); let value = xpath_result.unwrap(); let sec_names: Vec<String> = match value { sxd_xpath::Value::Nodeset(nodes) => { nodes .iter() .map(|node| node.expanded_name().unwrap().local_part().to_string()) .collect() }, _ => panic!("Failed: `Value` -> `Nodeset`") }; // ☢ 要注意: sxd-xpath の nodes の列挙順序は実行ごとに変わります。 let expected = ["x", "y", "z"]; assert_eq!(sec_names.len(), expected.len()); for element in expected.iter() { assert!(sec_names.contains(&element.to_string())); } } }
- 👍 嬉しいところ:
- 更新頻度、コミッター数はぼちぼち
- ❓ 嬉しいかもしれないけれど判断の難しいところ:
- めんどくさいので触れませんでしたが
Context
を使うとより変態的な実装もできそうです
- めんどくさいので触れませんでしたが
- ⚠ 注意が必要なところ:
どちらを使うのが嬉しそうか?
- XPath-3.1 対応が欲しい 👉 amxml
- Author / Comitter が活きていて欲しい 👉 sxd-xpath
- ユーザーコードを簡潔に済ませたい 👉 amxml
- XPath 以外の XML の DOM アクセスや read だけでなく write も欲しい 👉 sxd-xpath > amxml (基本的なDOM操作はどちらでもできます)
追記: どちらが速そう?; 2020-08-05
↓は
test benches::amxml_root ... bench: 37,902 ns/iter (+/- 4,120) test benches::amxml_sec ... bench: 25,728 ns/iter (+/- 1,255) test benches::sxd_root ... bench: 13,392 ns/iter (+/- 2,052) test benches::sxd_sec ... bench: 16,544 ns/iter (+/- 2,413)
参考
WSL で有効なコマンドの alias を巧く張る方法のメモ
↓こんな感じで alias を仕込むと WSL 環境でのみ alias を張れます:
if [[ $(grep -i Microsoft /proc/version) ]]; then alias code="/mnt/c/Users/usagi/AppData/Local/Programs/Microsoft\ VS\ Code/bin/code" alias display.blank="/mnt/c/Windows/System32/scrnsave.scr /s" alias power.sleep="/mnt/c/Users/usagi/app/PSTools/psshutdown.exe -d -t 0 -accepteula" alias power.hibernate="/mnt/c/Windows/System32/shutdown.exe /h" alias power.reboot="/mnt/c/Windows/System32/shutdown.exe /r" alias power.logoff="/mnt/c/Windows/System32/shutdown.exe /l" alias power.shutdown="/mnt/c/Windows/System32/shutdown.exe /s" fi
↑の例では:
/proc/version
を見て WSL を判定して$HOME
の環境を複数のPC、異なるOSで共有している人には管理コストをひとまとめにして分岐を書くのも便利な方法の1つと思います
code
(VSCode)を host 側で起動する (ホストWindows環境のパスを継承する設定で使っている場合は要らないやつです)display.blank
はブランクスクリーンのスクリーンセーバーを起動する事で、画面を一時的にすべて黒くしたい時に使うコマンドです- note: 写真撮影するときに画面が映り込んだり色が狂うのが嫌な時に便利です
power.*
系のコマンドは電源制御のお馴染みのコマンド群です。寝る前はたいていpower.sleep
/proc/version
WSL だと↓のように microsoft
が入るので判定に使用できています:
Linux version 4.19.84-microsoft-standard (oe-user@oe-host) (gcc version 8.2.0 (GCC)) #1 SMP Wed Nov 13 11:44:37 UTC 2019
WSLの判定方法は他にも複数思いつきます。わりとなんでもよいと思います。
Rust で XML パーサー使いたいならどの crate を使うと嬉しいかもしれないか、のメモ
状況
crates.io には執筆時点では複数の XML お取り扱いらしい crate が存在しています:
- xml-rs 4,013kDL
- quick-xml 681 kDL
- xml5ever 183kDL
- xmlparser 232kDL
- sxd-document 63kDL
- strong-xml 0.7kDL
- trashy-xml 0.7kDL
などなど。他にもいくつも登録されています。
1つの性能指標: quick-xml vs. sxd-document vs. xml5ever vs. xml-rs
quick-xml, sxd-document, xml5ever, xml-rs の4つについて性能指標を計測する crate があるようです:
note: RazrFalcon/choose-your-xml-rs では RazrFalcon/roxmltree#alternatives を事実上の移行先として案内していますが、残念ながら RazrFalcon/roxmltree は git clone
からとりあえず cargo bench
したところ大量のエラーでビルドできず面倒くさい気配がしたのでベンチマークとしては触れない事にしました。( roxmltree 本体の cargo build
は問題ないのだけど )
リポジトリーは既に archived ですが動作は可能でした。とりあえず clone
してそのまま試してみると:
cargo bench # xml-rs-0.7 quick-xml-0.10 xml5ever-0.11 sxd-document-0.2
running 11 tests test quick_xml_large ... bench: 1,922,040 ns/iter (+/- 53,752) test quick_xml_medium ... bench: 519,440 ns/iter (+/- 20,166) test quick_xml_small ... bench: 8,187 ns/iter (+/- 262) test sxd_document_medium ... bench: 2,767,680 ns/iter (+/- 295,128) test sxd_document_small ... bench: 46,143 ns/iter (+/- 6,440) test xml5ever_large ... bench: 9,244,405 ns/iter (+/- 629,496) test xml5ever_medium ... bench: 7,367,160 ns/iter (+/- 1,179,293) test xml5ever_small ... bench: 53,068 ns/iter (+/- 11,841) test xmlrs_large ... bench: 25,043,130 ns/iter (+/- 1,684,648) test xmlrs_medium ... bench: 11,846,650 ns/iter (+/- 2,163,808) test xmlrs_small ... bench: 87,464 ns/iter (+/- 16,859)
↑は依存が最新版に設定されていないので:
依存する XML crate 群を最新版に再定義し、 quick-xml と xml5ever のバージョンアップに伴う仕様変更に対応するパッチを充てて:
cargo bench # xml-rs-0.8.3 quick-xml-0.18.1 xml5ever-0.16.1 sxd-document-0.3.2
running 11 tests test quick_xml_large ... bench: 1,959,025 ns/iter (+/- 109,147) test quick_xml_medium ... bench: 510,790 ns/iter (+/- 87,736) test quick_xml_small ... bench: 7,367 ns/iter (+/- 154) test sxd_document_medium ... bench: 2,809,025 ns/iter (+/- 323,460) test sxd_document_small ... bench: 44,845 ns/iter (+/- 1,459) test xml5ever_large ... bench: 8,118,040 ns/iter (+/- 569,471) test xml5ever_medium ... bench: 6,755,910 ns/iter (+/- 168,634) test xml5ever_small ... bench: 47,349 ns/iter (+/- 2,106) test xmlrs_large ... bench: 24,848,280 ns/iter (+/- 1,742,538) test xmlrs_medium ... bench: 12,093,030 ns/iter (+/- 978,933) test xmlrs_small ... bench: 92,197 ns/iter (+/- 6,255)
巨大なXMLを扱う、パース速度が大事、そのような場合はこれらの選択肢から選べば quick-xml がとても優秀っぽい事がわかります。
minidom ≈ quick-xml vs. roxmltree ≈ xmlparser
xmlparser は↑のベンチマークの Author の RazrFalcon が書いた crate です。↑のベンチマークで"事実上の移行先"としてリンクされていた roxmltree
は xmlparser
をラップした高レベルパーサーという位置づけのようです。ここでちょっとした XML パーサー crate 群の整理:
high | low |
---|---|
roxmltree | xmlparser |
sdx-document | (独自の実装詳細) |
xmltree | xml-rs |
minidom | quick-xml |
↑こんな高レベルのパーサーと低レベルパーサーの関係になっていたようです。その上で、巨大な XML を大量を扱いたい場合にはやはり速度は大事なので、 quick-xml vs. xmlparser あるいは minidom vs. roxmltree を中心に比較すると、
- パース速度:
- 高レベルのパース: roxmltree(xmlparser) が minidom(quick-xml) より 1.59 倍くらい高速
- 低レベルのパース: quick-xml が xmlparser より 1.35 倍くらい高速
- 列挙速度:
- 任意要素の文字列マッチング: xmltree が roxmltree より 1.07 倍くらい高速、 minidom より 1.79 倍くらい高速
- 特定名要素の検索: romxltree が xmltree より 4.58 倍くらい高速、 minidom より 5.86 倍くらい高速
らしい。また、 roxmltree について RazrFalcon によると:
- xmlparser が quick-xml より遅い部分はより厳密なパースによるもの
- roxmltree は設計上は
panic
を起こさないし内部でunsafe
も使わない - roxmltree では XPath/XQuery, 変更や書き出し、仕様上完璧なXMLのサポートをする気は無いよ
と README に明記されています。
とりあえずの結論
- 高速な低レベルパーサーが必要な場合: quick-xml または次点で xmlparser
- 高速で安全安定っぽい高レベルサーバーが必要な場合: roxmltree
- もし特定の機能サポート都合で roxmltree ≈ xmlparser を使い難い場合:
- 高レベル向け: xmltree, minidom, sdx-document, または別の何かを探すか作るか
- 低レベル向け: quick-xml, xml-rs, または別の何かを探すか作るか
実際に使ってみてより詳細な気づきがあればその時にまたメモを追加しようと思います。
参考
VSCode via WSL2 で rust-analyzer が OUTPUT に Assertion failed: We don't expect to receive commands in CodeActions エラーを盛り盛り上げてきたら思い出すメモ
症状
WSL2 経由で動作する VSCode の rust-analyzer が一見動作しているように見えて OUTPUT に大量のエラーメッセージ:
Assertion failed: We don't expect to receive commands in CodeActions
を吐く。発症するとエラーが吐かれる度、作業中やログ監視などしている TERMINAL や PROBLEMS のタブからから OUTPUT タブへ表示も切り替わり不便です。
解決方法
さしあたり rust-analyzer
を手インストールすると発生しなくなるかもしれません:
cargo install rust-analyzer --git https://github.com/rust-analyzer/rust-analyzer/
参考
Rust で cdylib/wasm を吐く crate を分割したら依存先の機能を呼べなくなり、なんとなく extern crate を明示してみたら can't find されて5分くらい悩んだメモ
だいじな事:
crate
を分割したら、お呼ばれされる側の Cargo.toml で[lib]
のcrate-type
が明示的にrlib
を吐かない定義になっていないか確認しよう!
期待動作する例
# crate aaa に依存される側の crate bbb の Cargo.toml # ☆ ↓ src/lib.rs ありの crate では書かなくても同義扱いなのだけど、今回のメモの本質的な部分なのであえて明示しました。 [lib] crate-type = [ "rlib" ]
# crate bbb をに依存する側の crate A の Cargo.toml [dependencies] bbb = { path = "../bbb" }
// crate bbb に依存する crate aaa の main.rs // ◎ Rust を edition = "2018" で使う場合は extern carate は不要です; あっても問題ないけど extern crate bbb; // ◎ use しなくてもシンボルへの完全なパスを書けば使えます use bbb::some_module::some_sub_module::awesome_feature; // ◎ crate bbb に分割した何かを使う的な模擬コード let my_hoge = awesome_feature::hoge();
5分くらい悩んだダメな例
# crate aaa に依存される側の crate bbb の Cargo.toml [lib] crate-type = [ "cdylib" ] # ☆ rlib 出力が無いと依存してくれる側の .rs から extern して密結合できないのです。うっかり
解説
分割前の crate が .wasm を吐くとか、 .so/.dll/.dylib 的なそれを吐くのがプロジェクト単位での出力の場合、 [lib]
で crate-type = [ "cdylib" ]
とか定義しているはずです。そのような aaa から bbb を分割する際に、 Cargo.tml の内容を aaa の複製を元に書き出し、 cdylib
しか出力しない crate bbb を定義してしまうと、 crate aaa から [dependencies]
で依存する事はできますが、rlib
が無い状態では rust のソースコードから extern
して密結合的に使う事はできません。 crate bbb が cdylib
を出力する定義では crate aaa のビルドでも .wasm あるいは .so/.dll/.dylib 的なそれはビルドされます。その出力「も」欲しい場合もあるかとは思いますが、今回は crate aaa を整理のために crate bbb と分割し、 crate aaa から crate bbb へ依存するのが目的のため rlib
出力を追加定義または rlib
出力のみに変更するのが期待動作する分割に必要です。