生ポインタでnext
を付け替えてみようとしたが、できなかった……。
成果物
生ポインタでnext
を付け替えてみる
前回、std::mem::replace関数を使ってNode
のnext
を付け替えた。この関数はunsafe Rustにより生ポインタ操作することで所有権がらみの借用チェックを回避してくれる。その上unsafeな部分は隠蔽されているため、使う側はsafe Rustで書ける。
今回は、自前で生ポインタ操作によりnext
の付け替えをしてみたい。C言語のように。
前回コード
impl<T> Node<T> { fn new(item: T) -> Self { Self { item: item, next: None } } fn push(&mut self, item: T) { if self.next.is_none() { let new_node = Node::new(item); self.next = Some(Box::new(new_node)); } else { let tmp_node = std::mem::replace(&mut self.next, None); // self.nextに一旦Noneを入れる let mut new_node = Node::new(item); new_node.next = tmp_node; self.next = Some(Box::new(new_node)); } } }
今回コード
fn push(&mut self, item: T) { // fn push(mut self, item: T) { if self.next.is_none() { let new_node = Node::new(item); self.next = Some(Box::new(new_node)); } else { let mut new_node = Node::new(item); let ptr_self_next = Box::into_raw(self.next.unwrap()); // error[E0507]: cannot move out of borrowed content // let ptr_self_next = Box::into_raw(Box::new(*self.next.unwrap())); // error[E0507]: cannot move out of borrowed content unsafe { new_node.next = Some(Box::from_raw(ptr_self_next)); } self.next = Some(Box::new(new_node)); } } }
error[E0507]: cannot move out of borrowed content
fn push(&mut self, item: T) { if self.next.is_none() { let new_node = Node::new(item); self.next = Some(Box::new(new_node)); } else { let mut new_node = Node::new(item); let ptr_self_next = Box::into_raw(self.next.unwrap()); // error[E0507]: cannot move out of borrowed content // let ptr_self_next = Box::into_raw(self.next); // error[E0308]: mismatched types // let ptr_self_next = Box::into_raw(Box::new(*self.next.unwrap())); // error[E0507]: cannot move out of borrowed content unsafe { new_node.next = Some(Box::from_raw(ptr_self_next)); } self.next = Some(Box::new(new_node)); } } }
error[E0507]: cannot move out of borrowed content --> src/lib.rs:15:47 | 15 | let ptr_self_next = Box::into_raw(self.next.unwrap()); // error[E0507]: cannot move out of borrowed content | ^^^^^^^^^ cannot move out of borrowed content
self
は借用されているので所有権を奪えない。
だが、std::boxed::Box.into_raw()は所有権を奪うことでしか使えない。よって生ポインタが作れない……。
なら、&mut self
をmut self
にしちゃえばどうよ?
error[E0382]: borrow of moved value: first.next
fn push(mut self, item: T) { // &mut self の`&`をとって所有権を奪うようにした。すると呼出`first.push()`後で`first`の所有権が失われる。 if self.next.is_none() { let new_node = Node::new(item); self.next = Some(Box::new(new_node)); } else { let mut new_node = Node::new(item); let ptr_self_next = Box::into_raw(self.next.unwrap()); // error[E0507]: cannot move out of borrowed content unsafe { new_node.next = Some(Box::from_raw(ptr_self_next)); } self.next = Some(Box::new(new_node)); } }
error[E0382]: borrow of moved value: `first.next` --> src/lib.rs:78:9 | 72 | let mut first = Node::new(0); | --------- move occurs because `first` has type `Node<i32>`, which does not implement the `Copy` trait ... 76 | first.push(1); | ----- value moved here 77 | assert_eq!(first.item, 0); 78 | assert_eq!(first.next, Some(Box::new(Node { item: 1, next: None }))); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ value borrowed here after move | = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
push
を呼び出すと呼出元であるfirst
の所有権が奪われる。その後assert_eq!
で再びfirst
を使ったためエラー。
もちろん、このような用途を想定しているため、所有権を奪われては困る。
だが、std::boxed::Box.into_raw()は所有権を奪うことでしか使えない。よって生ポインタが作れない……。
error[E0609]: no field item
on type ()
なら、mut self
で所有権を奪ったあと、戻り値でself
を返せば所有権を返せるのでは?
impl<T> Node<T> { // ... fn push_unsafe(mut self, item: T) -> Self { if self.next.is_none() { let new_node = Node::new(item); self.next = Some(Box::new(new_node)); } else { let mut new_node = Node::new(item); let ptr_self_next = Box::into_raw(self.next.unwrap()); unsafe { new_node.next = Some(Box::from_raw(ptr_self_next)); } self.next = Some(Box::new(new_node)); } self } #[cfg(test)] mod tests { use super::*; // ... #[test] fn Node_push_unsafe_not_last() { let mut first = Node::new(0); assert_eq!(first.item, 0); assert_eq!(first.next, None); let first = first.push(1); assert_eq!(first.item, 0); assert_eq!(first.next, Some(Box::new(Node { item: 1, next: None }))); let second = first.next.as_ref().unwrap(); assert_eq!(second.item, 1); assert_eq!(second.next, None); let first = first.push(2); assert_eq!(first.item, 0); assert_eq!(first.next, Some(Box::new(Node { item: 2, next: Some(Box::new(Node { item: 1, next: None })) }))); let third = first.next.as_ref().unwrap(); assert_eq!(third.item, 2); assert_eq!(third.next, Some(Box::new(Node { item: 1, next: None }))); }
error[E0609]: no field `item` on type `()` --> src/lib.rs:146:26 | 146 | assert_eq!(first.item, 0); | ^^^^
どうやら戻り値が()
になっている模様。え、なにこの挙動。そういえば今までself
を返したことなんて無かった。何も返らなくなるのか……。つまり引数で参照でなく値を受け取ると、必ずメソッドの終端で死ぬってことか。大抵は奪われたら困ると思うのだが……。
そうなるとライフタイム指定子も無駄だろう。なにせself
が最長寿命なのに、そいつがメソッド内で死んでしまうのだから。やはりpush
では所有権をうばうわけにはいかない。
結論
生ポインタでnext
の付け替えができなかった……。
何か方法があるのか。あるいは、そんなコードが書けないから安全なのか。
対象環境
- Raspbierry pi 3 Model B+
- Raspbian stretch 9.0 2018-11-13
- bash 4.4.12(1)-release
- rustc 1.34.2 (6c2484dc3 2019-05-13)
- cargo 1.34.0 (6789d8a0a 2019-04-01)
$ uname -a Linux raspberrypi 4.19.42-v7+ #1219 SMP Tue May 14 21:20:58 BST 2019 armv7l GNU/Linux