質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

Q&A

解決済

1回答

3232閲覧

rustでborrowしてきた値のlifetimeを伸ばす方法

baban

総合スコア18

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

0グッド

0クリップ

投稿2019/05/28 23:01

前提・実現したいこと

rustの勉強中ですが、自分なりに課題で作っているコードで解決できない問題に当たったので
質問をさせてください。

発生している問題・エラーメッセージ

話を要約すると、for文の中でborrowしてきた値がfor文の外にlifetimeを持っているデータに引き渡せない
という問題です。

error[E0597]: `new_tree_node` does not live long enough --> catalan.rs:91:26 | 91 | stack.push(new_tree_node.borrow_mut()); | ^^^^^^^^^^^^^ borrowed value does not live long enough

該当のソースコード

プログラムの全体像は以下のリンクにあります。

https://github.com/baban/rust_test/blob/master/catalan.rs

要約だけ抜き取ったものを下に張り付けておきます。

このアルゴリズムでは、親のノードと、stackの2つが一時的に子のノードの所有者にならないとツリーは作れないので、RefCellを使って複数の所有者を持てるようにしました。
ただ、borrowしてきた値のlifetimeが短すぎてfor文を抜ける前に死んでしまうのでコンパイルが通りませんでした。
他のアルゴリズムを考えるのも検討したのですが、そもそもC言語やC++では動くはずのアルゴリズムです
そうであればRustでも、コンパイラに釈明を重ねれば動くはずで何かしら私の知識の抜けの問題と思い
軽く本を読みなおしてみたのですが、lifetimeをいじる方法は、関数の引数か構造体にしか言及がありませんでした。
何かしら書き方の問題で動かないだけの様な気がするのですが、教えていただけないでしょうか?

rust

1// vec![ノード,ノード,リーフ,リーフ,ノード,リーフ,リーフ] の様なVectorから 2// stackを使ってツリーを生成します。 3 4struct RefTreeNode<'a> { 5 left: RefTree<'a>, 6 right: RefTree<'a> 7} 8 9enum RefTree<'b> { 10 Node(Ref<'b, RefTreeNode<'b>>), 11 Leaf { value: i32 }, 12 Empty 13} 14 15fn build_tree(path: Vec<i32>) { 16 // pathから要素を一個取り出す 17 // nodeならstackに積む 18 // leafならstackからnodeをpopしてnodeのrightかleft空いている方に紐付ける 19 // nodeの片方しか埋まっていないときはstackにnodeを戻す 20 // nodeの両方が埋まったらstackに戻さない 21 // pathの最後までこの処理を行ったら、最後に持っているnodeがtreeのroot 22 let mut stack = Vec::<RefMut<RefTreeNode>>::new(); 23 let mut parent_node = RefTreeNode { left: RefTree::Empty, right: RefTree::Empty }; 24 let new_tree_node = RefCell::new(RefTreeNode { left: RefTree::Empty, right: RefTree::Empty }); 25 for pnt in path { 26 match pnt { 27 // node 28 2 => { 29 let latest_node = stack.pop(); 30 if let Some(mut parent_node) = latest_node { 31 match parent_node.left { 32 RefTree::Empty => { 33 let new_tree_node = RefCell::new(RefTreeNode { left: RefTree::Empty, right: RefTree::Empty }); 34 // parent_node.left = RefTree::Node(new_tree_node.borrow()); 35 stack.push(parent_node); 36 // stack.push(new_tree_node.borrow_mut()); 37 }, 38 _ => { 39 let new_tree_node = RefCell::new(RefTreeNode { left: RefTree::Empty, right: RefTree::Empty }); 40 // parent_node.left = RefTree::Node(new_tree_node.borrow()); 41 stack.push(parent_node); 42 // stack.push(new_tree_node.borrow_mut()); 43 }, 44 } 45 } else { 46 // 最初のnodeはstackが空なのでココが呼ばれる 47 let new_tree_node = RefCell::new(RefTreeNode { left: RefTree::Empty, right: RefTree::Empty }); 48 // stack.push(new_tree_node.borrow_mut()); 49 } 50 println!("stack length : {}", stack.len()); 51 }, 52 // leaf 53 1 => { 54 let latest_node = stack.pop(); 55 if let Some(mut parent_node) = latest_node { 56 match parent_node.left { 57 RefTree::Empty => { 58 parent_node.left = RefTree::Leaf { value: 4 }; 59 stack.push(parent_node); 60 }, 61 _ => { 62 parent_node.right = RefTree::Leaf { value: 4 }; 63 }, 64 } 65 println!("stack length : {}", stack.len()); 66 } 67 }, 68 _ =>{}, 69 }; 70 } 71}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

rust

1enum RefTree<'b> { 2 Node(Ref<'b, RefTreeNode<'b>>),

ツリーのデータ構造内で Ref を使っていますが、これは 対象を所有しない ポインタです。このような構造ですとライフタイムの関係でうまくいきません。なぜならツリーが要素を所有しておらず、ツリー自体の寿命よりも早く、その要素がメモリから解放されてしまうからです。

つまりデータ構造を変えて、ツリーが要素を所有するようにしないといけません。そうすればツリー自体が生きている間は、その要素もメモリ上に存在することが保証されます。そのためには 対象を所有する Box ポインタを使う必要があります。

おすすめはしませんが、ひとつの修正方法としては、いまの struct RefTreeNodeenum RefTree のフィールドで、RefTreeRefTreeNode を持つフィールドの型を、Box<RefTree>Box<RefTreeNode> に変えることが考えられます。

これをおすすめしない理由は以下のとおりです。

  • match 式で box syntax という非安定な機能が必要になる(nightly版のRustが必要)
  • スタックとヒープのメモリ配置が最適にならない

ではどういう構造をとったらいいかというと、こちらで紹介されている、「二分木の定義2 (Option 型を使う方法)」を真似るのがおすすめです。

rust

1// 節の定義 2struct Node<T : Ord> { 3 item: T, 4 left: Option<Box<Node<T>>>, 5 right: Option<Box<Node<T>>> 6} 7 8// 二分木 9struct Tree<T: Ord> { 10 root: Option<Box<Node<T>>> 11}

なぜこのような定義が良いのか一言で説明するのは難しく、英語ですみませんが、まずはこちらのチュートリアルを読んでもらうのがよいと思います。

これらのページでは単方向リンクリストのデータ構造を設計しています。このチュートリアルの2章(A Bad Singly-Linked Stack)と3章(An Ok Singly-Linked Stack)の内容はツリーの設計と実装にも大いに役立つと思いますので、上で紹介したページだけでなく、前後のページもぜひ読んでみてください。

そのあと「お気楽 Rust プログラミング超入門 -- 二分木」の設計と実装を参考に、ツリーの構造を変えて、再度チャレンジするのが良いと思います。回答として動作するプログラムを示せなくて残念なのですが、大幅な書き直しになってしまいそうなのです。

やってみてわからないところがあれば、再度質問してください。(コードが大きく変わりそうなので新しい質問を立ててもらったほうがよいかもしれません)


追記

RefCellを使って複数の所有者を持てるようにしました。

最初の回答で書き忘れましたが、RefCell は複数の所有者を持てる型ではなくて、内側のミュータビリティ(interior mutability)を提供する型です。複数の所有者を持てる型は Rc(Reference Counted、参照カウントの意味)になります。

今回のbuild_tree関数ではRcは使わなくても大丈夫そうです。

投稿2019/05/29 01:49

編集2019/05/29 02:24
tatsuya6502

総合スコア2035

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

baban

2019/06/01 01:21 編集

ありがとうございます。 ここ2,3日試行錯誤した結果、動くところまで出来ました。 https://github.com/baban/rust_test/blob/master/catalan.rs 詳しい解説をありがとうございます。 色々、理解が進みました。 オライリーの本を一通り読んだはずですが Refを使うと所有権を持たないからライフタイムは伸びない 本を読んだ限りそんなもん書いてあるかー、という感想です。 大幅な書き換えは、正しいコードに比べれば安いもので全然問題ないです。 ただ、結果としては、`Rc`、使って書きました stackとツリーの両方でデータを所有する関数自体を必要としてしまったので。 Rcのcloneメソッドの存在をやっと理解しました。 Rustは安全なC++を書けるようにならないと動くことすらないので難しいと噂ですが C++と違って、古い歴史の依存が無い分仕様がスッキリしていますし C++だとnewだけなのが、Box::newになったり パターンマッチでboxがマッチできないとか Rc<RefCell>とかのよく使いそうだけど長い書式があったり メモリにやさしくないパターンは絶対簡潔に書かせないという信念を感じます。 慣れた頃にこのあたりシンタックスシュガーが欲しいと思いそうです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問