C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

逆引き風 roxmltree の基礎的な使い方のメモ; rust のたぶん今の所いちばん速くて安全な XML ぱーさー

note: このメモは roxmltree-0.13.0 の時代に書きました。

はじめに知っておくとよいこと

  • 与えられた XMLDocumentNode木構造に分解して Node を基準に操作するための crate = roxmltree
  • NodeXML の Element (≈タグ)とは限りません。 Node が出てきたらカモシレナイ match/if が必要なパターンがあります
    • NodeType::Root = Document (文書全体)
    • NodeType::Element = XML Element (要素≈タグ)
    • NodeType::PI = XML Processing Instruction (<?xml ?> とか <?hogehoge ?> 的なタグもどきのやつ)
    • NodeType::Comment = XML Comment (コメント)
    • NodeType::Text = XML Text (テキスト≈<tag>abc</tag>abcの部分)
  • ancestor は上位(先祖)、 sbling は同位(姉妹)、 descendant は下位(子孫)
  • parent は直近の上位(親)、 child は直近の下位(子)

XMLを開く

let document: roxmltree::Document = roxmltree::Document::parse( "<root/>" );

XMLのルート要素の Node を取得する

let node: roxmltree::Node = d.root_element();

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();

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();

XML Namespace 群を確認する

for ns: &roxmltree::Namespace in document.root_element().namespace()
{
  println!("name={:?} uri={}", ns.name(), ns.uri());
}

Node の Attribute 群を取得する

for attribute: &roxmltree::Attribute in node.attributes()
{
  println!("name={} value={}", attribute.name(), attribute.value());
}

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 の上位(先祖)群、直近の上位(親)、下位(子孫)群

// 上位群
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() );
}

小さな 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 はやい
    • note: 但し roxmltree には XPath を扱うオプションはありません
    • note: minidom ≈ quick-xml
    • note: amxml は遅いし放棄気味だけど XPath-3.1 対応でコードも書きやすいです
  • sxd-document vs. sxd-xpath 👉 同じ操作でも XPath を噛むと4倍処理時間が長くなってしまう
    • note: 代わりに XPath を使えるという事は特に実行時のパース定義もしやすくなるなど利点は得られます
    • note: sxd は若干DOM操作のコードに癖がある気がしますが DOM ごりごりにも XPath にも同じエンジンを共有して対応できる点は嬉しい事もありそうです

そーす

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 で使い勝手を確認したメモです。

  1. amxml
  2. 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"]);
 }
}
  • 👍 嬉しいところ:
    • XPath-3.1 対応
    • read only という事になっているけれど、基本的な DOM 操作は実装されている; append_child, insert_as_previous_sibling, insert_as_next_sibling, delete_child, replace_with, set_attribute, delete_attribute
    • XPath を渡して結果を取得する実装が簡単で扱いやすい印象。実装コスト低く、保守性も良好。
      • eval_xpath でも each_node でも NodePtr または事実上 NodePtr として扱える Item が可換が列挙される (≈XPathだけでなくXPath+Rustコード実装によるDOM的な処理を行いやすい)
  • ⚠ 注意が必要なところ:
    • 2018 年から更新が無く、ごく簡単な Issue/PR も放置されている
    • note(†): 文字列値を直接取り出すとダブルクォート囲いなリテラル付きの表現になります
      • 例えばルート要素の名前を XPath で直接 /*/name() のように取得すると "\"abc\"" が取れます
// 参考おまけ: 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-1.0 のみ対応; (XSLT-1.0 には将来的に対応予定と README に記載あり。 libxml, libxslt の Rust による完全な置き換えを目標にしている)
    • evaluate_xpath から Value を取り出して Nodeset を取り出して Node を列挙する実装を書くのががややめんどくさい(XPath の規格通りの "QName に LocalPart があって…" などの構造を辿りやすいものの…)
    • XPath で直接要素名を取る /*/name() のような、XPath の結果がノード群になりえない XPath は失敗するようです

どちらを使うのが嬉しそうか?

  • XPath-3.1 対応が欲しい 👉 amxml
  • Author / Comitter が活きていて欲しい 👉 sxd-xpath
  • ユーザーコードを簡潔に済ませたい 👉 amxml
  • XPath 以外の XML の DOM アクセスや read だけでなく write も欲しい 👉 sxd-xpath > amxml (基本的なDOM操作はどちらでもできます)

追記: どちらが速そう?; 2020-08-05

  • たぶん sxd-xpath の方がはやいです (少なくとも↑の test の程度の小さな XML のパースなら)

↓は

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 が存在しています:

などなど。他にもいくつも登録されています。

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)

↑は依存が最新版に設定されていないので:

f:id:USAGI-WRP:20200803145309p:plain

依存する 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 です。↑のベンチマークで"事実上の移行先"としてリンクされていた roxmltreexmlparser をラップした高レベルパーサーという位置づけのようです。ここでちょっとした 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 に明記されています。

とりあえずの結論

  1. 高速な低レベルパーサーが必要な場合: quick-xml または次点で xmlparser
  2. 高速で安全安定っぽい高レベルサーバーが必要な場合: roxmltree
  3. もし特定の機能サポート都合で 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 出力のみに変更するのが期待動作する分割に必要です。